Merge pull request #517 from google/dev-webm-vp9-opus
Merge webm/vp9 native extensions into dev.
This commit is contained in:
commit
c992d9c433
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,6 +1,8 @@
|
||||
# Android generated
|
||||
bin
|
||||
gen
|
||||
libs
|
||||
obj
|
||||
lint.xml
|
||||
|
||||
# IntelliJ IDEA
|
||||
@ -16,6 +18,7 @@ gen-external-apklibs
|
||||
.classpath
|
||||
.settings
|
||||
.checkstyle
|
||||
.cproject
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
@ -37,3 +40,11 @@ proguard-project.txt
|
||||
.DS_Store
|
||||
dist
|
||||
tmp
|
||||
|
||||
# VP9 Extension
|
||||
extensions/vp9/src/main/jni/libvpx
|
||||
extensions/vp9/src/main/jni/libvpx_android_configs
|
||||
extensions/vp9/src/main/jni/libyuv
|
||||
|
||||
# Opus Extension
|
||||
extensions/opus/src/main/jni/libopus
|
||||
|
5
demo_misc/webm_sw_decoder/README.md
Normal file
5
demo_misc/webm_sw_decoder/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# WebM (VP9/Opus) Software Decoder Demo #
|
||||
|
||||
A demo app that shows how to use the ExoPlayer [VP9](../../extensions/vp9) and [Opus](../../extensions/opus) Extensions to enable VP9 and Opus playback in your app by bundling native libraries along with it.
|
||||
|
||||
The demo app depends on the VP9 and Opus Extensions being configured built correctly.
|
40
demo_misc/webm_sw_decoder/build.gradle
Normal file
40
demo_misc/webm_sw_decoder/build.gradle
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (C) 2014 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion "22.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
compile project(':opus-extension')
|
||||
compile project(':vp9-extension')
|
||||
}
|
9
demo_misc/webm_sw_decoder/src/main/.classpath
Normal file
9
demo_misc/webm_sw_decoder/src/main/.classpath
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
33
demo_misc/webm_sw_decoder/src/main/.project
Normal file
33
demo_misc/webm_sw_decoder/src/main/.project
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerExt-WebMDemo</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
55
demo_misc/webm_sw_decoder/src/main/AndroidManifest.xml
Normal file
55
demo_misc/webm_sw_decoder/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.demo.webm"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0"
|
||||
android:theme="@style/RootTheme">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<uses-feature android:glEsVersion="0x00020000"></uses-feature>
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22"/>
|
||||
|
||||
<application
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:allowBackup="false"
|
||||
android:icon="@drawable/ic_launcher">
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.webm.SampleChooserActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboardHidden">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.webm.VideoPlayer"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/PlayerTheme"/>
|
||||
|
||||
<activity android:name="com.google.android.exoplayer.demo.webm.FilePickerActivity"
|
||||
android:theme="@android:style/Theme.Dialog"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
|
||||
import com.google.android.exoplayer.DefaultLoadControl;
|
||||
import com.google.android.exoplayer.LoadControl;
|
||||
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSource;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.AdaptiveEvaluator;
|
||||
import com.google.android.exoplayer.chunk.MultiTrackChunkSource;
|
||||
import com.google.android.exoplayer.dash.DashChunkSource;
|
||||
import com.google.android.exoplayer.dash.mpd.AdaptationSet;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescription;
|
||||
import com.google.android.exoplayer.dash.mpd.MediaPresentationDescriptionParser;
|
||||
import com.google.android.exoplayer.dash.mpd.Period;
|
||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||
import com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.ext.vp9.LibvpxVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher;
|
||||
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Helper class that parses the manifest and builds the track renderers.
|
||||
*/
|
||||
public class DashRendererBuilder implements ManifestCallback<MediaPresentationDescription> {
|
||||
|
||||
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
|
||||
private static final int VIDEO_BUFFER_SEGMENTS = 200;
|
||||
private static final int AUDIO_BUFFER_SEGMENTS = 60;
|
||||
|
||||
private final String manifestUrl;
|
||||
private final String userAgent;
|
||||
private final VideoPlayer player;
|
||||
|
||||
public DashRendererBuilder(String manifestUrl, String userAgent, VideoPlayer player) {
|
||||
this.manifestUrl = manifestUrl;
|
||||
this.userAgent = userAgent;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void build() {
|
||||
MediaPresentationDescriptionParser parser = new MediaPresentationDescriptionParser();
|
||||
ManifestFetcher<MediaPresentationDescription> manifestFetcher =
|
||||
new ManifestFetcher<>(manifestUrl, new DefaultHttpDataSource(userAgent, null), parser);
|
||||
manifestFetcher.singleLoad(player.getMainHandler().getLooper(), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifestError(IOException e) {
|
||||
// TODO: do something meaningful here.
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSingleManifest(MediaPresentationDescription manifest) {
|
||||
LoadControl loadControl = new DefaultLoadControl(new DefaultAllocator(BUFFER_SEGMENT_SIZE));
|
||||
DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter(null, null);
|
||||
|
||||
// Obtain Representations for playback.
|
||||
ArrayList<Representation> audioRepresentationsList = new ArrayList<>();
|
||||
ArrayList<Representation> videoRepresentationsList = new ArrayList<>();
|
||||
Period period = manifest.periods.get(0);
|
||||
for (int i = 0; i < period.adaptationSets.size(); i++) {
|
||||
AdaptationSet adaptationSet = period.adaptationSets.get(i);
|
||||
int adaptationSetType = adaptationSet.type;
|
||||
for (int j = 0; j < adaptationSet.representations.size(); j++) {
|
||||
Representation representation = adaptationSet.representations.get(j);
|
||||
if (adaptationSetType == AdaptationSet.TYPE_AUDIO) {
|
||||
audioRepresentationsList.add(representation);
|
||||
} else if (adaptationSetType == AdaptationSet.TYPE_VIDEO) {
|
||||
videoRepresentationsList.add(representation);
|
||||
}
|
||||
}
|
||||
}
|
||||
Representation[] videoRepresentations = new Representation[videoRepresentationsList.size()];
|
||||
videoRepresentationsList.toArray(videoRepresentations);
|
||||
|
||||
// Build the video renderer.
|
||||
LibvpxVideoTrackRenderer videoRenderer = null;
|
||||
if (!videoRepresentationsList.isEmpty()) {
|
||||
DataSource videoDataSource = new DefaultUriDataSource(player, bandwidthMeter, userAgent);
|
||||
ChunkSource videoChunkSource;
|
||||
String mimeType = videoRepresentations[0].format.mimeType;
|
||||
if (mimeType.equals(MimeTypes.VIDEO_WEBM)) {
|
||||
videoChunkSource = new DashChunkSource(videoDataSource,
|
||||
new AdaptiveEvaluator(bandwidthMeter), videoRepresentations);
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected mime type: " + mimeType);
|
||||
}
|
||||
ChunkSampleSource videoSampleSource = new ChunkSampleSource(videoChunkSource, loadControl,
|
||||
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
|
||||
videoRenderer = new LibvpxVideoTrackRenderer(videoSampleSource,
|
||||
true, player.getMainHandler(), player, 50);
|
||||
}
|
||||
|
||||
// Build the audio renderer.
|
||||
MultiTrackChunkSource audioChunkSource = null;
|
||||
TrackRenderer audioRenderer = null;
|
||||
if (!audioRepresentationsList.isEmpty()) {
|
||||
DataSource audioDataSource = new DefaultUriDataSource(player, bandwidthMeter, userAgent);
|
||||
ChunkSource[] audioChunkSources = new ChunkSource[audioRepresentationsList.size()];
|
||||
FormatEvaluator audioEvaluator = new FormatEvaluator.FixedEvaluator();
|
||||
for (int i = 0; i < audioRepresentationsList.size(); i++) {
|
||||
Representation representation = audioRepresentationsList.get(i);
|
||||
audioChunkSources[i] = new DashChunkSource(audioDataSource,
|
||||
audioEvaluator, representation);
|
||||
}
|
||||
audioChunkSource = new MultiTrackChunkSource(audioChunkSources);
|
||||
SampleSource audioSampleSource = new ChunkSampleSource(audioChunkSource, loadControl,
|
||||
AUDIO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true);
|
||||
if (manifestUrl.contains("opus")) { // TODO: Need a better logic here.
|
||||
audioRenderer = new LibopusAudioTrackRenderer(audioSampleSource);
|
||||
} else {
|
||||
audioRenderer = new MediaCodecAudioTrackRenderer(audioSampleSource);
|
||||
}
|
||||
}
|
||||
|
||||
TrackRenderer[] renderers = new TrackRenderer[(audioRenderer == null) ? 1 : 2];
|
||||
renderers[0] = videoRenderer;
|
||||
if (audioRenderer != null) {
|
||||
renderers[1] = audioRenderer;
|
||||
}
|
||||
player.onRenderersBuilt(renderers);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ListActivity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A simple file picker.
|
||||
*/
|
||||
public class FilePickerActivity extends ListActivity {
|
||||
|
||||
public static final String FILENAME_EXTRA_ID = "filename";
|
||||
|
||||
private List<String> listItems;
|
||||
private List<File> itemPaths;
|
||||
private TextView currentPathView;
|
||||
private File root;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.file_picker_activity);
|
||||
setResult(Activity.RESULT_CANCELED);
|
||||
currentPathView = (TextView) findViewById(R.id.path);
|
||||
root = new File(Environment.getExternalStorageDirectory().getPath());
|
||||
setDirectory(root);
|
||||
}
|
||||
|
||||
private void setDirectory(File directory) {
|
||||
currentPathView.setText(getString(R.string.current_path, directory.getAbsolutePath()));
|
||||
listItems = new ArrayList<>();
|
||||
itemPaths = new ArrayList<>();
|
||||
File[] files = directory.listFiles();
|
||||
|
||||
if (!directory.getAbsolutePath().equals(root.getAbsolutePath())) {
|
||||
listItems.add(root.getAbsolutePath());
|
||||
itemPaths.add(root);
|
||||
listItems.add("../");
|
||||
itemPaths.add(new File(directory.getParent()));
|
||||
}
|
||||
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (!file.isHidden() && file.canRead()) {
|
||||
itemPaths.add(file);
|
||||
if (file.isDirectory()) {
|
||||
listItems.add(file.getName() + File.separator);
|
||||
} else {
|
||||
listItems.add(file.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setListAdapter(new ArrayAdapter<>(this, R.layout.rows, listItems));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||
File file = itemPaths.get(position);
|
||||
if (file.isDirectory() && file.canRead()) {
|
||||
setDirectory(itemPaths.get(position));
|
||||
} else {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(FILENAME_EXTRA_ID, file.getAbsolutePath());
|
||||
setResult(Activity.RESULT_OK, intent);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* An activity for selecting from a number of samples.
|
||||
*/
|
||||
public class SampleChooserActivity extends Activity {
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.sample_chooser_activity);
|
||||
|
||||
ListView sampleList = (ListView) findViewById(R.id.sample_list);
|
||||
final SampleAdapter sampleAdapter = new SampleAdapter(this);
|
||||
|
||||
sampleAdapter.add(new Header("Local VP9 Video only"));
|
||||
sampleAdapter.add(new Sample("S/W Color Conversion - upto 720p", false));
|
||||
sampleAdapter.add(new Sample("OpenGL", true));
|
||||
sampleAdapter.add(new Header("DASH - VP9 Only"));
|
||||
sampleAdapter.add(new Sample("Google Glass",
|
||||
"http://demos.webmproject.org/dash/201410/vp9_glass/manifest_vp9.mpd"));
|
||||
sampleAdapter.add(new Header("DASH - VP9 and Opus"));
|
||||
sampleAdapter.add(new Sample("Google Glass",
|
||||
"http://demos.webmproject.org/dash/201410/vp9_glass/manifest_vp9_opus.mpd"));
|
||||
sampleAdapter.add(new Header("DASH - VP9 and Vorbis"));
|
||||
sampleAdapter.add(new Sample("Google Glass",
|
||||
"http://demos.webmproject.org/dash/201410/vp9_glass/manifest_vp9_vorbis.mpd"));
|
||||
|
||||
sampleList.setAdapter(sampleAdapter);
|
||||
sampleList.setOnItemClickListener(new OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Object item = sampleAdapter.getItem(position);
|
||||
if (item instanceof Sample) {
|
||||
onSampleSelected((Sample) item);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onSampleSelected(Sample sample) {
|
||||
Intent playerIntent = new Intent(this, VideoPlayer.class)
|
||||
.putExtra(VideoPlayer.DASH_MANIFEST_URL_ID_EXTRA, sample.uri)
|
||||
.putExtra(VideoPlayer.USE_OPENGL_ID_EXTRA, sample.useOpenGL);
|
||||
startActivity(playerIntent);
|
||||
}
|
||||
|
||||
private static class SampleAdapter extends ArrayAdapter<Object> {
|
||||
|
||||
public SampleAdapter(Context context) {
|
||||
super(context, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View view = convertView;
|
||||
if (view == null) {
|
||||
int layoutId = getItemViewType(position) == 1 ? android.R.layout.simple_list_item_1
|
||||
: R.layout.sample_chooser_inline_header;
|
||||
view = LayoutInflater.from(getContext()).inflate(layoutId, null, false);
|
||||
}
|
||||
Object item = getItem(position);
|
||||
String name = null;
|
||||
if (item instanceof Sample) {
|
||||
name = ((Sample) item).description;
|
||||
} else if (item instanceof Header) {
|
||||
name = ((Header) item).name;
|
||||
}
|
||||
((TextView) view).setText(name);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return (getItem(position) instanceof Sample) ? 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Sample {
|
||||
|
||||
public final String description;
|
||||
public final String uri;
|
||||
public final boolean useOpenGL;
|
||||
|
||||
public Sample(String description, boolean useOpenGL) {
|
||||
this(description, null, useOpenGL);
|
||||
}
|
||||
|
||||
public Sample(String description, String uri) {
|
||||
this(description, uri, true); // always use OpenGL for DASH playbacks.
|
||||
}
|
||||
|
||||
public Sample(String description, String uri, boolean useOpenGL) {
|
||||
this.description = description;
|
||||
this.uri = uri;
|
||||
this.useOpenGL = useOpenGL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class Header {
|
||||
|
||||
public final String name;
|
||||
|
||||
public Header(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.demo.webm;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.VideoSurfaceView;
|
||||
import com.google.android.exoplayer.ext.opus.LibopusAudioTrackRenderer;
|
||||
import com.google.android.exoplayer.ext.vp9.LibvpxVideoTrackRenderer;
|
||||
import com.google.android.exoplayer.ext.vp9.VpxDecoderException;
|
||||
import com.google.android.exoplayer.ext.vp9.VpxVideoSurfaceView;
|
||||
import com.google.android.exoplayer.extractor.ExtractorSampleSource;
|
||||
import com.google.android.exoplayer.extractor.webm.WebmExtractor;
|
||||
import com.google.android.exoplayer.upstream.DefaultUriDataSource;
|
||||
import com.google.android.exoplayer.util.PlayerControl;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Sample player that shows how to use ExoPlayer Extensions to playback VP9 Video and Opus Audio.
|
||||
*/
|
||||
public class VideoPlayer extends Activity implements OnClickListener,
|
||||
LibvpxVideoTrackRenderer.EventListener, ExoPlayer.Listener {
|
||||
|
||||
public static final String DASH_MANIFEST_URL_ID_EXTRA = "manifest_url";
|
||||
public static final String USE_OPENGL_ID_EXTRA = "use_opengl";
|
||||
|
||||
private static final int FILE_PICKER_REQUEST = 1;
|
||||
private static final int EXTRACTOR_BUFFER_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
private boolean isDash;
|
||||
private String manifestUrl;
|
||||
private boolean useOpenGL;
|
||||
private String filename;
|
||||
|
||||
private ExoPlayer player;
|
||||
private Handler handler;
|
||||
private MediaController mediaController;
|
||||
private VideoSurfaceView videoSurfaceView;
|
||||
private VpxVideoSurfaceView vpxVideoSurfaceView;
|
||||
private TextView debugInfoView;
|
||||
private TextView playerStateView;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
manifestUrl = intent.getStringExtra(DASH_MANIFEST_URL_ID_EXTRA);
|
||||
isDash = manifestUrl != null;
|
||||
useOpenGL = intent.getBooleanExtra(USE_OPENGL_ID_EXTRA, true);
|
||||
|
||||
handler = new Handler();
|
||||
|
||||
setContentView(R.layout.activity_video_player);
|
||||
View root = findViewById(R.id.root);
|
||||
root.setOnTouchListener(new OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent motionEvent) {
|
||||
if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
toggleControlsVisibility();
|
||||
} else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
|
||||
view.performClick();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
mediaController = new MediaController(this);
|
||||
mediaController.setAnchorView(root);
|
||||
videoSurfaceView = (VideoSurfaceView) findViewById(R.id.surface_view);
|
||||
vpxVideoSurfaceView = (VpxVideoSurfaceView) findViewById(R.id.vpx_surface_view);
|
||||
debugInfoView = (TextView) findViewById(R.id.debug_info);
|
||||
playerStateView = (TextView) findViewById(R.id.player_state);
|
||||
|
||||
// Set the buttons' onclick listeners.
|
||||
((Button) findViewById(R.id.choose_file)).setOnClickListener(this);
|
||||
((Button) findViewById(R.id.play)).setOnClickListener(this);
|
||||
|
||||
// In case of DASH, start playback right away.
|
||||
if (isDash) {
|
||||
findViewById(R.id.buttons).setVisibility(View.GONE);
|
||||
((TextView) findViewById(R.id.filename)).setVisibility(View.GONE);
|
||||
startDashPlayback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
stopPlayback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.choose_file:
|
||||
Intent intent = new Intent();
|
||||
intent.setClass(this, FilePickerActivity.class);
|
||||
startActivityForResult(intent, FILE_PICKER_REQUEST);
|
||||
break;
|
||||
case R.id.play:
|
||||
startBasicPlayback();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case FILE_PICKER_REQUEST:
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
filename = data.getStringExtra(FilePickerActivity.FILENAME_EXTRA_ID);
|
||||
((TextView) findViewById(R.id.filename)).setText(
|
||||
getString(R.string.current_path, filename));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void startBasicPlayback() {
|
||||
if (filename == null) {
|
||||
Toast.makeText(this, "Choose a file!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
findViewById(R.id.buttons).setVisibility(View.GONE);
|
||||
player = ExoPlayer.Factory.newInstance(2);
|
||||
player.addListener(this);
|
||||
mediaController.setMediaPlayer(new PlayerControl(player));
|
||||
mediaController.setEnabled(true);
|
||||
ExtractorSampleSource sampleSource = new ExtractorSampleSource(
|
||||
Uri.fromFile(new File(filename)),
|
||||
new DefaultUriDataSource(this, Util.getUserAgent(this, "ExoPlayerExtWebMDemo")),
|
||||
new WebmExtractor(), 2, EXTRACTOR_BUFFER_SIZE);
|
||||
TrackRenderer videoRenderer =
|
||||
new LibvpxVideoTrackRenderer(sampleSource, true, handler, this, 50);
|
||||
if (useOpenGL) {
|
||||
player.sendMessage(videoRenderer, LibvpxVideoTrackRenderer.MSG_SET_VPX_SURFACE_VIEW,
|
||||
vpxVideoSurfaceView);
|
||||
videoSurfaceView.setVisibility(View.GONE);
|
||||
} else {
|
||||
player.sendMessage(
|
||||
videoRenderer, LibvpxVideoTrackRenderer.MSG_SET_SURFACE,
|
||||
videoSurfaceView.getHolder().getSurface());
|
||||
vpxVideoSurfaceView.setVisibility(View.GONE);
|
||||
}
|
||||
TrackRenderer audioRenderer = new LibopusAudioTrackRenderer(sampleSource);
|
||||
player.prepare(videoRenderer, audioRenderer);
|
||||
player.setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
private void startDashPlayback() {
|
||||
playerStateView.setText("Initializing");
|
||||
final String userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like"
|
||||
+ " Gecko) Chrome/38.0.2125.104 Safari/537.36";
|
||||
DashRendererBuilder rendererBuilder = new DashRendererBuilder(manifestUrl, userAgent, this);
|
||||
rendererBuilder.build();
|
||||
}
|
||||
|
||||
public void onRenderersBuilt(TrackRenderer[] renderers) {
|
||||
videoSurfaceView.setVisibility(View.GONE);
|
||||
player = ExoPlayer.Factory.newInstance(renderers.length);
|
||||
player.addListener(this);
|
||||
mediaController.setMediaPlayer(new PlayerControl(player));
|
||||
mediaController.setEnabled(true);
|
||||
player.sendMessage(renderers[0], LibvpxVideoTrackRenderer.MSG_SET_VPX_SURFACE_VIEW,
|
||||
vpxVideoSurfaceView);
|
||||
player.prepare(renderers);
|
||||
player.setPlayWhenReady(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDroppedFrames(int count, long elapsed) {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(int width, int height) {
|
||||
if (isDash || useOpenGL) {
|
||||
vpxVideoSurfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (width * 1.0f) / height);
|
||||
} else {
|
||||
videoSurfaceView.setVideoWidthHeightRatio(height == 0 ? 1 : (width * 1.0f) / height);
|
||||
}
|
||||
debugInfoView.setText("Video: " + width + " x " + height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawnToSurface(Surface surface) {
|
||||
// do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecoderError(VpxDecoderException e) {
|
||||
debugInfoView.setText("Libvpx decode failure. Giving up.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int state) {
|
||||
String playerState = "";
|
||||
switch (player.getPlaybackState()) {
|
||||
case ExoPlayer.STATE_BUFFERING:
|
||||
playerState = "buffering";
|
||||
break;
|
||||
case ExoPlayer.STATE_ENDED:
|
||||
playerState = "ended";
|
||||
break;
|
||||
case ExoPlayer.STATE_IDLE:
|
||||
playerState = "idle";
|
||||
break;
|
||||
case ExoPlayer.STATE_PREPARING:
|
||||
playerState = "preparing";
|
||||
break;
|
||||
case ExoPlayer.STATE_READY:
|
||||
playerState = "ready";
|
||||
break;
|
||||
}
|
||||
playerStateView.setText("Player State: " + playerState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException exception) {
|
||||
debugInfoView.setText("Exoplayer Playback error. Giving up.");
|
||||
// TODO: show a retry button here.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayWhenReadyCommitted() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public Handler getMainHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
private void stopPlayback() {
|
||||
if (player != null) {
|
||||
player.stop();
|
||||
player.release();
|
||||
player = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void toggleControlsVisibility() {
|
||||
if (mediaController != null) {
|
||||
if (mediaController.isShowing()) {
|
||||
mediaController.hide();
|
||||
} else {
|
||||
mediaController.show(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
17
demo_misc/webm_sw_decoder/src/main/project.properties
Normal file
17
demo_misc/webm_sw_decoder/src/main/project.properties
Normal file
@ -0,0 +1,17 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-22
|
||||
android.library.reference.1=../../../../library/src/main
|
||||
android.library.reference.2=../../../../extensions/opus/src/main
|
||||
android.library.reference.3=../../../../extensions/vp9/src/main
|
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:keepScreenOn="true">
|
||||
|
||||
<com.google.android.exoplayer.VideoSurfaceView
|
||||
android:id="@+id/surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center|center_vertical"/>
|
||||
|
||||
<com.google.android.exoplayer.ext.vp9.VpxVideoSurfaceView
|
||||
android:id="@+id/vpx_surface_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center|center_vertical"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="#88000000">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:id="@+id/choose_file"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/choose_file"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/play"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/play"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filename"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="10dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/player_state"
|
||||
android:paddingRight="10dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/debug_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/path"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<ListView
|
||||
android:id="@android:id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
20
demo_misc/webm_sw_decoder/src/main/res/layout/rows.xml
Normal file
20
demo_misc/webm_sw_decoder/src/main/res/layout/rows.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/rowtext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="25sp"/>
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ListView android:id="@+id/sample_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"
|
||||
android:padding="8dp"
|
||||
android:focusable="true"
|
||||
android:background="#339999FF"/>
|
26
demo_misc/webm_sw_decoder/src/main/res/values/strings.xml
Normal file
26
demo_misc/webm_sw_decoder/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
|
||||
<string name="app_name">WebM ExoPlayer Demo</string>
|
||||
<string name="choose_file">Choose File</string>
|
||||
<string name="play">Play</string>
|
||||
<string name="current_path">
|
||||
Path: <xliff:g id="path" example="/sdcard/test.webm">%1$s</xliff:g>
|
||||
</string>
|
||||
|
||||
</resources>
|
26
demo_misc/webm_sw_decoder/src/main/res/values/styles.xml
Normal file
26
demo_misc/webm_sw_decoder/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="RootTheme" parent="android:Theme.Holo"/>
|
||||
|
||||
<style name="PlayerTheme" parent="@style/RootTheme">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
131
extensions/opus/README.md
Normal file
131
extensions/opus/README.md
Normal file
@ -0,0 +1,131 @@
|
||||
# ExoPlayer Opus Extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The Opus Extension is a [Track Renderer][] implementation that helps you bundle libopus (the Opus decoding library) into your app and use it along with ExoPlayer to play Opus audio on Android devices.
|
||||
|
||||
[Track Renderer]: http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/TrackRenderer.html
|
||||
|
||||
## Build Instructions (Android Studio and Eclipse) ##
|
||||
|
||||
Building the Opus Extension involves building libopus and JNI bindings using the Android NDK and linking it into your app. The following steps will tell you how to do that using Android Studio or Eclipse.
|
||||
|
||||
* Checkout ExoPlayer along with Extensions
|
||||
|
||||
```
|
||||
git clone https://github.com/google/ExoPlayer.git
|
||||
```
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
```
|
||||
cd "<path to exoplayer checkout>"
|
||||
EXOPLAYER_ROOT="$(pwd)"
|
||||
OPUS_EXT_PATH="${EXOPLAYER_ROOT}/extensions/opus/src/main"
|
||||
```
|
||||
|
||||
* Download the [Android NDK][] and set its location in an environment variable:
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
```
|
||||
|
||||
* Fetch libopus
|
||||
|
||||
```
|
||||
cd "${OPUS_EXT_PATH}/jni" && \
|
||||
git clone git://git.opus-codec.org/opus.git libopus
|
||||
```
|
||||
|
||||
* Run the script to convert arm assembly to NDK compatible format
|
||||
|
||||
```
|
||||
cd ${OPUS_EXT_PATH}/jni && ./convert_android_asm.sh
|
||||
```
|
||||
|
||||
### Android Studio ###
|
||||
|
||||
For Android Studio, we build the native libraries from the command line and then Gradle will pick it up when building your app using Android Studio.
|
||||
|
||||
* Build the JNI native libraries
|
||||
|
||||
```
|
||||
cd "${OPUS_EXT_PATH}"/jni && \
|
||||
${NDK_PATH}/ndk-build APP_ABI=all -j4
|
||||
```
|
||||
|
||||
* In your project, you can add a dependency to the Opus Extension by using a rule like this:
|
||||
|
||||
```
|
||||
// in settings.gradle
|
||||
include ':..:ExoPlayer:library'
|
||||
include ':..:ExoPlayer:opus-extension'
|
||||
|
||||
// in build.gradle
|
||||
dependencies {
|
||||
compile project(':..:ExoPlayer:library')
|
||||
compile project(':..:ExoPlayer:opus-extension')
|
||||
}
|
||||
```
|
||||
|
||||
* Now, when you build your app, the Opus extension will be built and the native libraries will be packaged along with the APK.
|
||||
|
||||
### Eclipse ###
|
||||
|
||||
* The following steps assume that you have installed Eclipse and configured it with the [Android SDK][] and [Android NDK ][]:
|
||||
* Navigate to File->Import->General->Existing Projects into Workspace
|
||||
* Select the root directory of the repository
|
||||
* Import the following projects:
|
||||
* ExoPlayerLib
|
||||
* ExoPlayerExt-Opus
|
||||
* If you are able to build ExoPlayerExt-Opus project, then you're all set.
|
||||
* (Optional) To speed up the NDK build:
|
||||
* Right click on ExoPlayerExt-Opus in the Project Explorer pane and choose Properties
|
||||
* Click on C/C++ Build
|
||||
* Uncheck `Use default build command`
|
||||
* In `Build Command` enter: `ndk-build -j4` (adjust 4 to a reasonable number depending on the number of cores in your computer)
|
||||
* Click Apply
|
||||
|
||||
You can now create your own Android App project and add ExoPlayerLib along with ExoPlayerExt-Opus as a dependencies to use ExoPlayer along with the Opus Extension.
|
||||
|
||||
|
||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||
<!---
|
||||
Work around to point to two different links for the same text.
|
||||
-->
|
||||
[Android NDK ]: http://tools.android.com/recent/usingthendkplugin
|
||||
[Android SDK]: http://developer.android.com/sdk/installing/index.html?pkg=tools
|
||||
|
||||
## Building for various Architectures ##
|
||||
|
||||
### Android Studio ###
|
||||
|
||||
The manual invocation of `ndk-build` will build the library for all architectures and the correct one will be picked up from the APK based on the device its running on.
|
||||
|
||||
### Eclipse ###
|
||||
|
||||
libopus can be built for the following architectures:
|
||||
|
||||
* armeabi (the default - does not include neon optimizations)
|
||||
* armeabi-v7a (choose this to enable neon optimizations)
|
||||
* mips
|
||||
* x86
|
||||
* all (will result in a larger binary but will cover all architectures)
|
||||
|
||||
You can build for a specific architecture in two ways:
|
||||
|
||||
* Method 1 (edit `Application.mk`)
|
||||
* Edit `${OPUS_EXT_PATH}/jni/Application.mk` and add the following line `APP_ABI := <arch>` (where `<arch>` is one of the above 4 architectures)
|
||||
* Method 2 (pass NDK build flag)
|
||||
* Right click on ExoPlayerExt-Opus in the Project Explorer pane and choose Properties
|
||||
* Click on C/C++ Build
|
||||
* Uncheck `Use default build command`
|
||||
* In `Build Command` enter: `ndk-build APP_ABI=<arch>` (where `<arch>` is one of the above 4 architectures)
|
||||
* Click Apply
|
||||
|
||||
## Other Things to Note ##
|
||||
|
||||
* Every time there is a change to the libopus checkout:
|
||||
* Arm assembly should be converted by running `convert_android_asm.sh`
|
||||
* Clean and re-build the project.
|
||||
* If you want to use your own version of libopus, place it in `${OPUS_EXT_PATH}/jni/libopus`.
|
45
extensions/opus/build.gradle
Normal file
45
extensions/opus/build.gradle
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2014 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion "22.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 22
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'src/main/libs'
|
||||
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
}
|
||||
|
10
extensions/opus/src/main/.classpath
Normal file
10
extensions/opus/src/main/.classpath
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="/ExoPlayerLib"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
57
extensions/opus/src/main/.cproject
Normal file
57
extensions/opus/src/main/.cproject
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
|
||||
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||
<cconfiguration id="com.android.toolchain.gcc.423224913">
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="com.android.toolchain.gcc.423224913" moduleId="org.eclipse.cdt.core.settings" name="Default">
|
||||
<externalSettings/>
|
||||
<extensions>
|
||||
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
</extensions>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="" id="com.android.toolchain.gcc.423224913" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
<folderInfo id="com.android.toolchain.gcc.423224913.1376674556" name="/" resourcePath="">
|
||||
<toolChain id="com.android.toolchain.gcc.1798416430" name="Android GCC" superClass="com.android.toolchain.gcc">
|
||||
<targetPlatform binaryParser="org.eclipse.cdt.core.ELF" id="com.android.targetPlatform.1132129264" isAbstract="false" superClass="com.android.targetPlatform"/>
|
||||
<builder buildPath="${workspace_loc:/ExoPlayerExt-Opus}/jni" id="com.android.builder.532503968" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Android Builder" superClass="com.android.builder">
|
||||
<outputEntries>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="obj"/>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="libs"/>
|
||||
</outputEntries>
|
||||
</builder>
|
||||
<tool id="com.android.gcc.compiler.906450637" name="Android GCC Compiler" superClass="com.android.gcc.compiler">
|
||||
<inputType id="com.android.gcc.inputType.835889068" superClass="com.android.gcc.inputType"/>
|
||||
</tool>
|
||||
</toolChain>
|
||||
</folderInfo>
|
||||
<sourceEntries>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="jni"/>
|
||||
</sourceEntries>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
</cconfiguration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<project id="ExoPlayerExt-Opus.null.1840202624" name="ExoPlayerExt-Opus"/>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
|
||||
<storageModule moduleId="scannerConfiguration">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
<scannerConfigBuildInfo instanceId="com.android.toolchain.gcc.423224913;com.android.toolchain.gcc.423224913.1376674556;com.android.gcc.compiler.906450637;com.android.gcc.inputType.835889068">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="com.android.AndroidPerProjectProfile"/>
|
||||
</scannerConfigBuildInfo>
|
||||
</storageModule>
|
||||
<storageModule moduleId="refreshScope" versionNumber="2">
|
||||
<configuration configurationName="Default">
|
||||
<resource resourceType="PROJECT" workspacePath="/ExoPlayerExt-Opus"/>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
</cproject>
|
97
extensions/opus/src/main/.project
Normal file
97
extensions/opus/src/main/.project
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerExt-Opus</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>?children?</key>
|
||||
<value>?name?=outputEntries\|?children?=?name?=entry\\\\\\\|\\\|?name?=entry\\\\\\\|\\\|\||</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>?name?</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.append_environment</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildArguments</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildCommand</key>
|
||||
<value>ndk-build</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
|
||||
<value>clean</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.contents</key>
|
||||
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
|
||||
<value>false</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.stopOnError</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
@ -0,0 +1,12 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
22
extensions/opus/src/main/AndroidManifest.xml
Normal file
22
extensions/opus/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.ext.opus">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22"/>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,442 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.opus;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.MediaFormatHolder;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.audio.AudioTrack;
|
||||
import com.google.android.exoplayer.ext.opus.OpusDecoderWrapper.InputBuffer;
|
||||
import com.google.android.exoplayer.ext.opus.OpusDecoderWrapper.OutputBuffer;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.os.Handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Decodes and renders audio using the native Opus decoder.
|
||||
*
|
||||
* @author vigneshv@google.com (Vignesh Venkatasubramanian)
|
||||
*/
|
||||
public class LibopusAudioTrackRenderer extends TrackRenderer {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link LibopusAudioTrackRenderer} events.
|
||||
*/
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Invoked when the {@link AudioTrack} fails to initialize.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onAudioTrackInitializationError(AudioTrack.InitializationException e);
|
||||
|
||||
/**
|
||||
* Invoked when an {@link AudioTrack} write fails.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onAudioTrackWriteError(AudioTrack.WriteException e);
|
||||
|
||||
/**
|
||||
* Invoked when decoding fails.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onDecoderError(OpusDecoderException e);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of a message that can be passed to an instance of this class via
|
||||
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
|
||||
* should be a {@link Float} with 0 being silence and 1 being unity gain.
|
||||
*/
|
||||
public static final int MSG_SET_VOLUME = 1;
|
||||
|
||||
private final SampleSource source;
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final MediaFormatHolder formatHolder;
|
||||
|
||||
private MediaFormat format;
|
||||
private OpusDecoderWrapper decoder;
|
||||
private InputBuffer inputBuffer;
|
||||
private OutputBuffer outputBuffer;
|
||||
|
||||
private int trackIndex;
|
||||
private long currentPositionUs;
|
||||
private boolean inputStreamEnded;
|
||||
private boolean outputStreamEnded;
|
||||
private boolean sourceIsReady;
|
||||
private boolean notifyDiscontinuityToDecoder;
|
||||
|
||||
private AudioTrack audioTrack;
|
||||
private int audioSessionId;
|
||||
|
||||
/**
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
*/
|
||||
public LibopusAudioTrackRenderer(SampleSource source) {
|
||||
this(source, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
*/
|
||||
public LibopusAudioTrackRenderer(SampleSource source, Handler eventHandler,
|
||||
EventListener eventListener) {
|
||||
this.source = source;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
this.audioTrack = new AudioTrack();
|
||||
formatHolder = new MediaFormatHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isTimeSource() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doPrepare(long positionUs) throws ExoPlaybackException {
|
||||
try {
|
||||
boolean sourcePrepared = source.prepare(positionUs);
|
||||
if (!sourcePrepared) {
|
||||
return TrackRenderer.STATE_UNPREPARED;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.getTrackCount(); i++) {
|
||||
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_OPUS)
|
||||
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.AUDIO_WEBM)) {
|
||||
trackIndex = i;
|
||||
return TrackRenderer.STATE_PREPARED;
|
||||
}
|
||||
}
|
||||
|
||||
return TrackRenderer.STATE_IGNORE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sourceIsReady = source.continueBuffering(positionUs);
|
||||
checkForDiscontinuity();
|
||||
if (format == null) {
|
||||
readFormat();
|
||||
} else {
|
||||
// Create the decoder.
|
||||
if (decoder == null) {
|
||||
// For opus, the format can contain upto 3 entries in initializationData in the following
|
||||
// exact order:
|
||||
// 1) Opus Header Information (required)
|
||||
// 2) Codec Delay in nanoseconds (required if Seek Preroll is present)
|
||||
// 3) Seek Preroll in nanoseconds (required if Codec Delay is present)
|
||||
List<byte[]> initializationData = format.initializationData;
|
||||
if (initializationData.size() < 1) {
|
||||
throw new ExoPlaybackException("Missing initialization data");
|
||||
}
|
||||
long codecDelayNs = -1;
|
||||
long seekPreRollNs = -1;
|
||||
if (initializationData.size() == 3) {
|
||||
if (initializationData.get(1).length != Long.SIZE
|
||||
|| initializationData.get(2).length != Long.SIZE) {
|
||||
throw new ExoPlaybackException("Invalid Codec Delay or Seek Preroll");
|
||||
}
|
||||
codecDelayNs = ByteBuffer.wrap(initializationData.get(1)).getLong();
|
||||
seekPreRollNs = ByteBuffer.wrap(initializationData.get(2)).getLong();
|
||||
}
|
||||
decoder =
|
||||
new OpusDecoderWrapper(initializationData.get(0), codecDelayNs, seekPreRollNs);
|
||||
decoder.start();
|
||||
}
|
||||
renderBuffer();
|
||||
|
||||
// Queue input buffers.
|
||||
while (feedInputBuffer()) {}
|
||||
}
|
||||
} catch (AudioTrack.InitializationException e) {
|
||||
notifyAudioTrackInitializationError(e);
|
||||
throw new ExoPlaybackException(e);
|
||||
} catch (AudioTrack.WriteException e) {
|
||||
notifyAudioTrackWriteError(e);
|
||||
throw new ExoPlaybackException(e);
|
||||
} catch (OpusDecoderException e) {
|
||||
notifyDecoderError(e);
|
||||
throw new ExoPlaybackException(e);
|
||||
} catch (IOException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderBuffer() throws OpusDecoderException, AudioTrack.InitializationException,
|
||||
AudioTrack.WriteException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (outputBuffer == null) {
|
||||
outputBuffer = decoder.dequeueOutputBuffer();
|
||||
if (outputBuffer == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputBuffer.getFlag(OpusDecoderWrapper.FLAG_END_OF_STREAM)) {
|
||||
outputStreamEnded = true;
|
||||
decoder.releaseOutputBuffer(outputBuffer);
|
||||
outputBuffer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audioTrack.isInitialized()) {
|
||||
if (audioSessionId != AudioTrack.SESSION_ID_NOT_SET) {
|
||||
audioTrack.initialize(audioSessionId);
|
||||
} else {
|
||||
audioSessionId = audioTrack.initialize();
|
||||
}
|
||||
if (getState() == TrackRenderer.STATE_STARTED) {
|
||||
audioTrack.play();
|
||||
}
|
||||
}
|
||||
|
||||
int handleBufferResult;
|
||||
handleBufferResult = audioTrack.handleBuffer(outputBuffer.data,
|
||||
outputBuffer.data.position(), outputBuffer.size, outputBuffer.timestampUs);
|
||||
|
||||
// If we are out of sync, allow currentPositionUs to jump backwards.
|
||||
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
|
||||
currentPositionUs = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
// Release the buffer if it was consumed.
|
||||
if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
|
||||
decoder.releaseOutputBuffer(outputBuffer);
|
||||
outputBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean feedInputBuffer() throws IOException, OpusDecoderException {
|
||||
if (inputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputBuffer == null) {
|
||||
inputBuffer = decoder.getInputBuffer();
|
||||
if (inputBuffer == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int result = source.readData(trackIndex, currentPositionUs, formatHolder,
|
||||
inputBuffer.sampleHolder, false);
|
||||
if (result == SampleSource.NOTHING_READ) {
|
||||
return false;
|
||||
}
|
||||
if (result == SampleSource.DISCONTINUITY_READ) {
|
||||
flushDecoder();
|
||||
return true;
|
||||
}
|
||||
if (result == SampleSource.FORMAT_READ) {
|
||||
format = formatHolder.format;
|
||||
return true;
|
||||
}
|
||||
if (result == SampleSource.END_OF_STREAM) {
|
||||
inputBuffer.setFlag(OpusDecoderWrapper.FLAG_END_OF_STREAM);
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
inputBuffer = null;
|
||||
inputStreamEnded = true;
|
||||
return false;
|
||||
}
|
||||
if (notifyDiscontinuityToDecoder) {
|
||||
notifyDiscontinuityToDecoder = false;
|
||||
inputBuffer.setFlag(OpusDecoderWrapper.FLAG_RESET_DECODER);
|
||||
}
|
||||
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
inputBuffer = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkForDiscontinuity() throws IOException {
|
||||
if (decoder == null) {
|
||||
return;
|
||||
}
|
||||
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, true);
|
||||
if (result == SampleSource.DISCONTINUITY_READ) {
|
||||
flushDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
private void flushDecoder() {
|
||||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
decoder.flush();
|
||||
notifyDiscontinuityToDecoder = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnded() {
|
||||
return outputStreamEnded && (!audioTrack.hasPendingData()
|
||||
|| !audioTrack.hasEnoughDataToBeginPlayback());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReady() {
|
||||
return audioTrack.hasPendingData() || (format != null && sourceIsReady);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getDurationUs() {
|
||||
return source.getTrackInfo(trackIndex).durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getCurrentPositionUs() {
|
||||
long audioTrackCurrentPositionUs = audioTrack.getCurrentPositionUs(isEnded());
|
||||
if (audioTrackCurrentPositionUs != AudioTrack.CURRENT_POSITION_NOT_SET) {
|
||||
// Make sure we don't ever report time moving backwards.
|
||||
currentPositionUs = Math.max(currentPositionUs, audioTrackCurrentPositionUs);
|
||||
}
|
||||
return currentPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getBufferedPositionUs() {
|
||||
long sourceBufferedPosition = source.getBufferedPositionUs();
|
||||
return sourceBufferedPosition == UNKNOWN_TIME_US || sourceBufferedPosition == END_OF_TRACK_US
|
||||
? sourceBufferedPosition : Math.max(sourceBufferedPosition, getCurrentPositionUs());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void seekTo(long positionUs) throws ExoPlaybackException {
|
||||
audioTrack.reset();
|
||||
currentPositionUs = positionUs;
|
||||
source.seekToUs(positionUs);
|
||||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
sourceIsReady = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabled(long positionUs, boolean joining) {
|
||||
source.enable(trackIndex, positionUs);
|
||||
sourceIsReady = false;
|
||||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
currentPositionUs = Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStarted() {
|
||||
audioTrack.play();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopped() {
|
||||
audioTrack.pause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReleased() {
|
||||
source.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisabled() {
|
||||
if (decoder != null) {
|
||||
decoder.release();
|
||||
decoder = null;
|
||||
}
|
||||
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||
try {
|
||||
audioTrack.release();
|
||||
} finally {
|
||||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
format = null;
|
||||
source.disable(trackIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void readFormat() throws IOException {
|
||||
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, false);
|
||||
if (result == SampleSource.FORMAT_READ) {
|
||||
format = formatHolder.format;
|
||||
audioTrack.reconfigure(format.getFrameworkMediaFormatV16());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
|
||||
if (messageType == MSG_SET_VOLUME) {
|
||||
audioTrack.setVolume((Float) message);
|
||||
} else {
|
||||
super.handleMessage(messageType, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onAudioTrackInitializationError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onAudioTrackWriteError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDecoderError(final OpusDecoderException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onDecoderError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.opus;
|
||||
|
||||
import com.google.android.exoplayer.ext.opus.OpusDecoderWrapper.OpusHeader;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* JNI Wrapper for the libopus Opus decoder.
|
||||
*
|
||||
* @author vigneshv@google.com (Vignesh Venkatasubramanian)
|
||||
*/
|
||||
/* package */ class OpusDecoder {
|
||||
|
||||
private final long nativeDecoderContext;
|
||||
|
||||
static {
|
||||
System.loadLibrary("opus");
|
||||
System.loadLibrary("opusJNI");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Opus Decoder.
|
||||
*
|
||||
* @param opusHeader OpusHeader used to initialize the decoder.
|
||||
* @throws OpusDecoderException if the decoder initialization fails.
|
||||
*/
|
||||
public OpusDecoder(OpusHeader opusHeader) throws OpusDecoderException {
|
||||
nativeDecoderContext = opusInit(
|
||||
opusHeader.sampleRate, opusHeader.channelCount, opusHeader.numStreams,
|
||||
opusHeader.numCoupled, opusHeader.gain, opusHeader.streamMap);
|
||||
if (nativeDecoderContext == 0) {
|
||||
throw new OpusDecoderException("failed to initialize opus decoder");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an Opus Encoded Stream.
|
||||
*
|
||||
* @param inputBuffer buffer containing the encoded data. Must be allocated using allocateDirect.
|
||||
* @param inputSize size of the input buffer.
|
||||
* @param outputBuffer buffer to write the decoded data. Must be allocated using allocateDirect.
|
||||
* @param outputSize Maximum capacity of the output buffer.
|
||||
* @return number of decoded bytes.
|
||||
* @throws OpusDecoderException if decode fails.
|
||||
*/
|
||||
public int decode(ByteBuffer inputBuffer, int inputSize, ByteBuffer outputBuffer,
|
||||
int outputSize) throws OpusDecoderException {
|
||||
int result = opusDecode(nativeDecoderContext, inputBuffer, inputSize, outputBuffer, outputSize);
|
||||
if (result < 0) {
|
||||
throw new OpusDecoderException(opusGetErrorMessage(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the native decoder.
|
||||
*/
|
||||
public void close() {
|
||||
opusClose(nativeDecoderContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the native decode on discontinuity (during seek for example).
|
||||
*/
|
||||
public void reset() {
|
||||
opusReset(nativeDecoderContext);
|
||||
}
|
||||
|
||||
private native long opusInit(int sampleRate, int channelCount, int numStreams, int numCoupled,
|
||||
int gain, byte[] streamMap);
|
||||
private native int opusDecode(long decoder, ByteBuffer inputBuffer, int inputSize,
|
||||
ByteBuffer outputBuffer, int outputSize);
|
||||
private native void opusClose(long decoder);
|
||||
private native void opusReset(long decoder);
|
||||
private native String opusGetErrorMessage(int errorCode);
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.opus;
|
||||
|
||||
/**
|
||||
* Thrown when an Opus decoder error occurs.
|
||||
*/
|
||||
public class OpusDecoderException extends Exception {
|
||||
|
||||
public OpusDecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,383 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.opus;
|
||||
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Wraps {@link OpusDecoder}, exposing a higher level decoder interface.
|
||||
*
|
||||
* @author vigneshv@google.com (Vignesh Venkatasubramanian)
|
||||
*/
|
||||
/* package */ class OpusDecoderWrapper extends Thread {
|
||||
|
||||
public static final int FLAG_END_OF_STREAM = 1;
|
||||
public static final int FLAG_RESET_DECODER = 2;
|
||||
|
||||
private static final int INPUT_BUFFER_SIZE = 960 * 6;
|
||||
private static final int OUTPUT_BUFFER_SIZE = 960 * 6 * 2;
|
||||
private static final int NUM_BUFFERS = 16;
|
||||
private static final int DEFAULT_SEEK_PRE_ROLL = 3840;
|
||||
|
||||
private final Object lock;
|
||||
private final OpusHeader opusHeader;
|
||||
|
||||
private final LinkedList<InputBuffer> queuedInputBuffers;
|
||||
private final LinkedList<OutputBuffer> queuedOutputBuffers;
|
||||
private final InputBuffer[] availableInputBuffers;
|
||||
private final OutputBuffer[] availableOutputBuffers;
|
||||
private int availableInputBufferCount;
|
||||
private int availableOutputBufferCount;
|
||||
|
||||
private int skipSamples;
|
||||
private boolean flushDecodedOutputBuffer;
|
||||
private boolean released;
|
||||
|
||||
private int seekPreRoll;
|
||||
|
||||
private OpusDecoderException decoderException;
|
||||
|
||||
/**
|
||||
* @param headerBytes Opus header data that is used to initialize the decoder. For WebM Container,
|
||||
* this comes from the CodecPrivate Track element.
|
||||
* @param codecDelayNs Delay in nanoseconds added by the codec at the beginning. For WebM
|
||||
* Container, this comes from the CodecDelay Track Element. Can be -1 in which case the value
|
||||
* from the codec header will be used.
|
||||
* @param seekPreRollNs Duration in nanoseconds of samples to discard when there is a
|
||||
* discontinuity. For WebM Container, this comes from the SeekPreRoll Track Element. Can be -1
|
||||
* in which case the default value of 80ns will be used.
|
||||
* @throws OpusDecoderException if an exception occurs when initializing the decoder.
|
||||
*/
|
||||
public OpusDecoderWrapper(byte[] headerBytes, long codecDelayNs,
|
||||
long seekPreRollNs) throws OpusDecoderException {
|
||||
lock = new Object();
|
||||
opusHeader = parseOpusHeader(headerBytes);
|
||||
skipSamples = (codecDelayNs == -1) ? opusHeader.skipSamples : nsToSamples(codecDelayNs);
|
||||
seekPreRoll = (seekPreRoll == -1) ? DEFAULT_SEEK_PRE_ROLL : nsToSamples(seekPreRollNs);
|
||||
queuedInputBuffers = new LinkedList<>();
|
||||
queuedOutputBuffers = new LinkedList<>();
|
||||
availableInputBuffers = new InputBuffer[NUM_BUFFERS];
|
||||
availableOutputBuffers = new OutputBuffer[NUM_BUFFERS];
|
||||
availableInputBufferCount = NUM_BUFFERS;
|
||||
availableOutputBufferCount = NUM_BUFFERS;
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
availableInputBuffers[i] = new InputBuffer();
|
||||
availableOutputBuffers[i] = new OutputBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public InputBuffer getInputBuffer() throws OpusDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
if (availableInputBufferCount == 0) {
|
||||
return null;
|
||||
}
|
||||
InputBuffer inputBuffer = availableInputBuffers[--availableInputBufferCount];
|
||||
inputBuffer.reset();
|
||||
return inputBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public void queueInputBuffer(InputBuffer inputBuffer) throws OpusDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
queuedInputBuffers.addLast(inputBuffer);
|
||||
maybeNotifyDecodeLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public OutputBuffer dequeueOutputBuffer() throws OpusDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
if (queuedOutputBuffers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return queuedOutputBuffers.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseOutputBuffer(OutputBuffer outputBuffer) throws OpusDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
outputBuffer.reset();
|
||||
availableOutputBuffers[availableOutputBufferCount++] = outputBuffer;
|
||||
maybeNotifyDecodeLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
synchronized (lock) {
|
||||
flushDecodedOutputBuffer = true;
|
||||
while (!queuedInputBuffers.isEmpty()) {
|
||||
availableInputBuffers[availableInputBufferCount++] = queuedInputBuffers.removeFirst();
|
||||
}
|
||||
while (!queuedOutputBuffers.isEmpty()) {
|
||||
availableOutputBuffers[availableOutputBufferCount++] = queuedOutputBuffers.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
synchronized (lock) {
|
||||
released = true;
|
||||
lock.notify();
|
||||
}
|
||||
try {
|
||||
join();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeThrowDecoderError() throws OpusDecoderException {
|
||||
if (decoderException != null) {
|
||||
throw decoderException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the decode loop if there exists a queued input buffer and an available output buffer
|
||||
* to decode into.
|
||||
* <p>
|
||||
* Should only be called whilst synchronized on the lock object.
|
||||
*/
|
||||
private void maybeNotifyDecodeLoop() {
|
||||
if (!queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
OpusDecoder decoder = null;
|
||||
try {
|
||||
decoder = new OpusDecoder(opusHeader);
|
||||
while (decodeBuffer(decoder)) {
|
||||
// Do nothing.
|
||||
}
|
||||
} catch (OpusDecoderException e) {
|
||||
synchronized (lock) {
|
||||
decoderException = e;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Shouldn't ever happen.
|
||||
} finally {
|
||||
if (decoder != null) {
|
||||
decoder.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean decodeBuffer(OpusDecoder decoder) throws InterruptedException,
|
||||
OpusDecoderException {
|
||||
InputBuffer inputBuffer;
|
||||
OutputBuffer outputBuffer;
|
||||
|
||||
// Wait until we have an input buffer to decode, and an output buffer to decode into.
|
||||
synchronized (lock) {
|
||||
while (!released && (queuedInputBuffers.isEmpty() || availableOutputBufferCount == 0)) {
|
||||
lock.wait();
|
||||
}
|
||||
if (released) {
|
||||
return false;
|
||||
}
|
||||
inputBuffer = queuedInputBuffers.removeFirst();
|
||||
outputBuffer = availableOutputBuffers[--availableOutputBufferCount];
|
||||
flushDecodedOutputBuffer = false;
|
||||
}
|
||||
|
||||
// Decode.
|
||||
if (inputBuffer.getFlag(FLAG_END_OF_STREAM)) {
|
||||
outputBuffer.setFlag(FLAG_END_OF_STREAM);
|
||||
} else {
|
||||
if (inputBuffer.getFlag(FLAG_RESET_DECODER)) {
|
||||
decoder.reset();
|
||||
// When seeking to 0, skip number of samples as specified in opus header. When seeking to
|
||||
// any other time, skip number of samples as specified by seek preroll.
|
||||
skipSamples = (inputBuffer.sampleHolder.timeUs == 0) ? opusHeader.skipSamples : seekPreRoll;
|
||||
}
|
||||
SampleHolder sampleHolder = inputBuffer.sampleHolder;
|
||||
sampleHolder.data.position(sampleHolder.data.position() - sampleHolder.size);
|
||||
outputBuffer.timestampUs = sampleHolder.timeUs;
|
||||
outputBuffer.size = decoder.decode(sampleHolder.data, sampleHolder.size,
|
||||
outputBuffer.data, outputBuffer.data.capacity());
|
||||
outputBuffer.data.position(0);
|
||||
if (skipSamples > 0) {
|
||||
int bytesPerSample = opusHeader.channelCount * 2;
|
||||
int skipBytes = skipSamples * bytesPerSample;
|
||||
if (outputBuffer.size < skipBytes) {
|
||||
skipSamples -= outputBuffer.size / bytesPerSample;
|
||||
outputBuffer.size = 0;
|
||||
} else {
|
||||
skipSamples = 0;
|
||||
outputBuffer.data.position(skipBytes);
|
||||
outputBuffer.size -= skipBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
if (flushDecodedOutputBuffer
|
||||
|| inputBuffer.sampleHolder.isDecodeOnly()
|
||||
|| outputBuffer.size == 0) {
|
||||
// In the following cases, we make the output buffer available again rather than queuing it
|
||||
// to be consumed:
|
||||
// 1) A flush occured whilst we were decoding.
|
||||
// 2) The input sample has decodeOnly flag set.
|
||||
// 3) We skip the entire buffer due to skipSamples being greater than bytes decoded.
|
||||
outputBuffer.reset();
|
||||
availableOutputBuffers[availableOutputBufferCount++] = outputBuffer;
|
||||
} else {
|
||||
// Queue the decoded output buffer to be consumed.
|
||||
queuedOutputBuffers.addLast(outputBuffer);
|
||||
}
|
||||
// Make the input buffer available again.
|
||||
availableInputBuffers[availableInputBufferCount++] = inputBuffer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private OpusHeader parseOpusHeader(byte[] headerBytes) throws OpusDecoderException {
|
||||
final int maxChannelCount = 8;
|
||||
final int maxChannelCountWithDefaultLayout = 2;
|
||||
final int headerSize = 19;
|
||||
final int headerChannelCountOffset = 9;
|
||||
final int headerSkipSamplesOffset = 10;
|
||||
final int headerGainOffset = 16;
|
||||
final int headerChannelMappingOffset = 18;
|
||||
final int headerNumStreamsOffset = headerSize;
|
||||
final int headerNumCoupledOffset = headerNumStreamsOffset + 1;
|
||||
final int headerStreamMapOffset = headerNumStreamsOffset + 2;
|
||||
OpusHeader opusHeader = new OpusHeader();
|
||||
try {
|
||||
// Opus streams are always decoded at 48000 hz.
|
||||
opusHeader.sampleRate = 48000;
|
||||
opusHeader.channelCount = headerBytes[headerChannelCountOffset];
|
||||
if (opusHeader.channelCount > maxChannelCount) {
|
||||
throw new OpusDecoderException("Invalid channel count: " + opusHeader.channelCount);
|
||||
}
|
||||
opusHeader.skipSamples = readLittleEndian16(headerBytes, headerSkipSamplesOffset);
|
||||
opusHeader.gain = readLittleEndian16(headerBytes, headerGainOffset);
|
||||
opusHeader.channelMapping = headerBytes[headerChannelMappingOffset];
|
||||
|
||||
if (opusHeader.channelMapping == 0) {
|
||||
// If there is no channel mapping, use the defaults.
|
||||
if (opusHeader.channelCount > maxChannelCountWithDefaultLayout) {
|
||||
throw new OpusDecoderException("Invalid Header, missing stream map.");
|
||||
}
|
||||
opusHeader.numStreams = 1;
|
||||
opusHeader.numCoupled = (opusHeader.channelCount > 1) ? 1 : 0;
|
||||
opusHeader.streamMap[0] = 0;
|
||||
opusHeader.streamMap[1] = 1;
|
||||
} else {
|
||||
// Read the channel mapping.
|
||||
opusHeader.numStreams = headerBytes[headerNumStreamsOffset];
|
||||
opusHeader.numCoupled = headerBytes[headerNumCoupledOffset];
|
||||
for (int i = 0; i < opusHeader.channelCount; i++) {
|
||||
opusHeader.streamMap[i] = headerBytes[headerStreamMapOffset + i];
|
||||
}
|
||||
}
|
||||
return opusHeader;
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new OpusDecoderException("Header size is too small.");
|
||||
}
|
||||
}
|
||||
|
||||
private int readLittleEndian16(byte[] input, int offset) {
|
||||
int value = input[offset];
|
||||
value |= input[offset + 1] << 8;
|
||||
return value;
|
||||
}
|
||||
|
||||
private int nsToSamples(long ns) {
|
||||
return (int) (ns * opusHeader.sampleRate / 1000000000);
|
||||
}
|
||||
|
||||
/* package */ static final class InputBuffer {
|
||||
|
||||
public final SampleHolder sampleHolder;
|
||||
|
||||
public int flags;
|
||||
|
||||
public InputBuffer() {
|
||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||
sampleHolder.data = ByteBuffer.allocateDirect(INPUT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
sampleHolder.data.clear();
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
public void setFlag(int flag) {
|
||||
flags |= flag;
|
||||
}
|
||||
|
||||
public boolean getFlag(int flag) {
|
||||
return (flags & flag) == flag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* package */ static final class OutputBuffer {
|
||||
|
||||
public ByteBuffer data;
|
||||
public int size;
|
||||
public long timestampUs;
|
||||
public int flags;
|
||||
|
||||
public OutputBuffer() {
|
||||
data = ByteBuffer.allocateDirect(OUTPUT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
data.clear();
|
||||
size = 0;
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
public void setFlag(int flag) {
|
||||
flags |= flag;
|
||||
}
|
||||
|
||||
public boolean getFlag(int flag) {
|
||||
return (flags & flag) == flag;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* package */ static final class OpusHeader {
|
||||
|
||||
public int sampleRate;
|
||||
public int channelCount;
|
||||
public int skipSamples;
|
||||
public int gain;
|
||||
public int channelMapping;
|
||||
public int numStreams;
|
||||
public int numCoupled;
|
||||
public byte[] streamMap;
|
||||
|
||||
public OpusHeader() {
|
||||
streamMap = new byte[8];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
34
extensions/opus/src/main/jni/Android.mk
Normal file
34
extensions/opus/src/main/jni/Android.mk
Normal file
@ -0,0 +1,34 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
WORKING_DIR := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
APP_PLATFORM := android-10
|
||||
|
||||
# build libopus.so
|
||||
LOCAL_PATH := $(WORKING_DIR)
|
||||
include libopus.mk
|
||||
|
||||
# build libopusJNI.so
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_PATH := $(WORKING_DIR)
|
||||
LOCAL_MODULE := libopusJNI
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
LOCAL_SRC_FILES := opus_jni.cc
|
||||
LOCAL_LDLIBS := -llog -lz -lm
|
||||
LOCAL_SHARED_LIBRARIES := libopus
|
||||
include $(BUILD_SHARED_LIBRARY)
|
19
extensions/opus/src/main/jni/Application.mk
Normal file
19
extensions/opus/src/main/jni/Application.mk
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
APP_OPTIM := release
|
||||
APP_STL := gnustl_static
|
||||
APP_CPPFLAGS := -frtti
|
47
extensions/opus/src/main/jni/convert_android_asm.sh
Executable file
47
extensions/opus/src/main/jni/convert_android_asm.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
set -e
|
||||
ASM_CONVERTER="./libopus/celt/arm/arm2gnu.pl"
|
||||
|
||||
if [[ ! -x "${ASM_CONVERTER}" ]]; then
|
||||
echo "Please make sure you have checked out libopus."
|
||||
exit
|
||||
fi
|
||||
|
||||
while read file; do
|
||||
# This check is required because the ASM conversion script doesn't seem to be
|
||||
# idempotent.
|
||||
if [[ ! "${file}" =~ .*_gnu\.s$ ]]; then
|
||||
gnu_file="${file%.s}_gnu.s"
|
||||
${ASM_CONVERTER} "${file}" > "${gnu_file}"
|
||||
# The ASM conversion script replaces includes with *_gnu.S. So, replace
|
||||
# occurences of "*-gnu.S" with "*_gnu.s".
|
||||
sed -i "s/-gnu\.S/_gnu\.s/g" "${gnu_file}"
|
||||
rm -f "${file}"
|
||||
fi
|
||||
done < <(find . -iname '*.s')
|
||||
|
||||
# Generate armopts.s from armopts.s.in
|
||||
sed \
|
||||
-e "s/@OPUS_ARM_MAY_HAVE_EDSP@/1/g" \
|
||||
-e "s/@OPUS_ARM_MAY_HAVE_MEDIA@/1/g" \
|
||||
-e "s/@OPUS_ARM_MAY_HAVE_NEON@/1/g" \
|
||||
libopus/celt/arm/armopts.s.in > libopus/celt/arm/armopts.s.temp
|
||||
${ASM_CONVERTER} "libopus/celt/arm/armopts.s.temp" > "libopus/celt/arm/armopts_gnu.s"
|
||||
rm "libopus/celt/arm/armopts.s.temp"
|
||||
echo "Converted all ASM files and generated armopts.s successfully."
|
50
extensions/opus/src/main/jni/libopus.mk
Normal file
50
extensions/opus/src/main/jni/libopus.mk
Normal file
@ -0,0 +1,50 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
LOCAL_PATH := $(call my-dir)/libopus
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
include $(LOCAL_PATH)/celt_headers.mk
|
||||
include $(LOCAL_PATH)/celt_sources.mk
|
||||
include $(LOCAL_PATH)/opus_headers.mk
|
||||
include $(LOCAL_PATH)/opus_sources.mk
|
||||
include $(LOCAL_PATH)/silk_headers.mk
|
||||
include $(LOCAL_PATH)/silk_sources.mk
|
||||
|
||||
LOCAL_MODULE := libopus
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_CFLAGS := -DOPUS_BUILD -DFIXED_POINT -DUSE_ALLOCA -DHAVE_LRINT \
|
||||
-DHAVE_LRINTF
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src \
|
||||
$(LOCAL_PATH)/silk $(LOCAL_PATH)/celt \
|
||||
$(LOCAL_PATH)/silk/fixed
|
||||
LOCAL_SRC_FILES := $(CELT_SOURCES) $(OPUS_SOURCES) $(OPUS_SOURCES_FLOAT) \
|
||||
$(SILK_SOURCES) $(SILK_SOURCES_FIXED)
|
||||
|
||||
ifneq ($(findstring armeabi-v7a, $(TARGET_ARCH_ABI)),)
|
||||
LOCAL_SRC_FILES += $(CELT_SOURCES_ARM)
|
||||
LOCAL_SRC_FILES += celt/arm/armopts_gnu.s.neon
|
||||
LOCAL_SRC_FILES += $(subst .s,_gnu.s.neon,$(CELT_SOURCES_ARM_ASM))
|
||||
LOCAL_CFLAGS += -DOPUS_ARM_ASM -DOPUS_ARM_INLINE_ASM -DOPUS_ARM_INLINE_EDSP \
|
||||
-DOPUS_ARM_INLINE_MEDIA -DOPUS_ARM_INLINE_NEON \
|
||||
-DOPUS_ARM_MAY_HAVE_NEON -DOPUS_ARM_MAY_HAVE_MEDIA \
|
||||
-DOPUS_ARM_MAY_HAVE_EDSP
|
||||
endif
|
||||
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
96
extensions/opus/src/main/jni/opus_jni.cc
Normal file
96
extensions/opus/src/main/jni/opus_jni.cc
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "opus.h" // NOLINT
|
||||
#include "opus_multistream.h" // NOLINT
|
||||
|
||||
#define LOG_TAG "libopus_native"
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
|
||||
__VA_ARGS__))
|
||||
|
||||
#define FUNC(RETURN_TYPE, NAME, ...) \
|
||||
extern "C" { \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_opus_OpusDecoder_ ## NAME \
|
||||
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
|
||||
} \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_opus_OpusDecoder_ ## NAME \
|
||||
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
|
||||
|
||||
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
static int channelCount;
|
||||
|
||||
FUNC(jlong, opusInit, jint sampleRate, jint channelCount, jint numStreams,
|
||||
jint numCoupled, jint gain, jbyteArray jStreamMap) {
|
||||
int status = OPUS_INVALID_STATE;
|
||||
::channelCount = channelCount;
|
||||
jbyte* streamMapBytes = env->GetByteArrayElements(jStreamMap, 0);
|
||||
uint8_t* streamMap = reinterpret_cast<uint8_t*>(streamMapBytes);
|
||||
OpusMSDecoder* decoder = opus_multistream_decoder_create(
|
||||
sampleRate, channelCount, numStreams, numCoupled, streamMap, &status);
|
||||
env->ReleaseByteArrayElements(jStreamMap, streamMapBytes, 0);
|
||||
if (!decoder || status != OPUS_OK) {
|
||||
LOGE("Failed to create Opus Decoder; status=%s", opus_strerror(status));
|
||||
return 0;
|
||||
}
|
||||
status = opus_multistream_decoder_ctl(decoder, OPUS_SET_GAIN(gain));
|
||||
if (status != OPUS_OK) {
|
||||
LOGE("Failed to set Opus header gain; status=%s", opus_strerror(status));
|
||||
return 0;
|
||||
}
|
||||
return reinterpret_cast<intptr_t>(decoder);
|
||||
}
|
||||
|
||||
FUNC(jint, opusDecode, jlong jDecoder, jobject jInputBuffer, jint inputSize,
|
||||
jobject jOutputBuffer, jint outputSize) {
|
||||
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
|
||||
const uint8_t* inputBuffer =
|
||||
reinterpret_cast<const uint8_t*>(
|
||||
env->GetDirectBufferAddress(jInputBuffer));
|
||||
int16_t* outputBuffer = reinterpret_cast<int16_t*>(
|
||||
env->GetDirectBufferAddress(jOutputBuffer));
|
||||
int numFrames = opus_multistream_decode(decoder, inputBuffer, inputSize,
|
||||
outputBuffer, outputSize, 0);
|
||||
return (numFrames < 0) ? numFrames : numFrames * 2 * channelCount;
|
||||
}
|
||||
|
||||
FUNC(void, opusClose, jlong jDecoder) {
|
||||
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
|
||||
opus_multistream_decoder_destroy(decoder);
|
||||
}
|
||||
|
||||
FUNC(void, opusReset, jlong jDecoder) {
|
||||
OpusMSDecoder* decoder = reinterpret_cast<OpusMSDecoder*>(jDecoder);
|
||||
opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
FUNC(jstring, opusGetErrorMessage, jint errorCode) {
|
||||
return env->NewStringUTF(opus_strerror(errorCode));
|
||||
}
|
16
extensions/opus/src/main/project.properties
Normal file
16
extensions/opus/src/main/project.properties
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-22
|
||||
android.library=true
|
||||
android.library.reference.1=../../../../library/src/main
|
2
extensions/opus/src/main/res/.README.txt
Normal file
2
extensions/opus/src/main/res/.README.txt
Normal file
@ -0,0 +1,2 @@
|
||||
This file is needed to make sure the res directory is present.
|
||||
The file is ignored by the Android toolchain because its name starts with a dot.
|
134
extensions/vp9/README.md
Normal file
134
extensions/vp9/README.md
Normal file
@ -0,0 +1,134 @@
|
||||
# ExoPlayer VP9 Extension #
|
||||
|
||||
## Description ##
|
||||
|
||||
The VP9 Extension is a [Track Renderer][] implementation that helps you bundle libvpx (the VP9 decoding library) into your app and use it along with ExoPlayer to play VP9 video on Android devices.
|
||||
|
||||
[Track Renderer]: http://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer/TrackRenderer.html
|
||||
|
||||
## Build Instructions (Android Studio and Eclipse) ##
|
||||
|
||||
Building the VP9 Extension involves building libvpx and JNI bindings using the Android NDK and linking it into your app. The following steps will tell you how to do that using Android Studio or Eclipse.
|
||||
|
||||
* Checkout ExoPlayer along with Extensions
|
||||
|
||||
```
|
||||
git clone https://github.com/google/ExoPlayer.git
|
||||
```
|
||||
|
||||
* Set the following environment variables:
|
||||
|
||||
```
|
||||
cd "<path to exoplayer checkout>"
|
||||
EXOPLAYER_ROOT="$(pwd)"
|
||||
VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
|
||||
```
|
||||
|
||||
* Download the [Android NDK][] and set its location in an environment variable:
|
||||
|
||||
```
|
||||
NDK_PATH="<path to Android NDK>"
|
||||
```
|
||||
|
||||
* Fetch libvpx and libyuv
|
||||
|
||||
```
|
||||
cd "${VP9_EXT_PATH}/jni" && \
|
||||
git clone https://chromium.googlesource.com/webm/libvpx libvpx && \
|
||||
git clone http://git.chromium.org/external/libyuv.git libyuv
|
||||
```
|
||||
|
||||
* Run a script that generates necessary configuration files for libvpx
|
||||
|
||||
```
|
||||
cd ${VP9_EXT_PATH}/jni && \
|
||||
./generate_libvpx_android_configs.sh "${NDK_PATH}"
|
||||
```
|
||||
|
||||
### Android Studio ###
|
||||
|
||||
For Android Studio, we build the native libraries from the command line and then Gradle will pick it up when building your app using Android Studio.
|
||||
|
||||
* Build the JNI native libraries
|
||||
|
||||
```
|
||||
cd "${VP9_EXT_PATH}"/jni && \
|
||||
${NDK_PATH}/ndk-build APP_ABI=all -j4
|
||||
```
|
||||
|
||||
* In your project, you can add a dependency to the VP9 Extension by using a the following rule
|
||||
|
||||
```
|
||||
// in settings.gradle
|
||||
include ':..:ExoPlayer:library'
|
||||
include ':..:ExoPlayer:vp9-extension'
|
||||
|
||||
// in build.gradle
|
||||
dependencies {
|
||||
compile project(':..:ExoPlayer:library')
|
||||
compile project(':..:ExoPlayer:vp9-extension')
|
||||
}
|
||||
```
|
||||
|
||||
* Now, when you build your app, the VP9 extension will be built and the native libraries will be packaged along with the APK.
|
||||
|
||||
### Eclipse ###
|
||||
|
||||
* The following steps assume that you have installed Eclipse and configured it with the [Android SDK][] and [Android NDK ][]:
|
||||
* Navigate to File->Import->General->Existing Projects into Workspace
|
||||
* Select the root directory of the repository
|
||||
* Import the following projects:
|
||||
* ExoPlayerLib
|
||||
* ExoPlayerExt-VP9
|
||||
* If you are able to build ExoPlayerExt-VP9 project, then you're all set.
|
||||
* (Optional) To speed up the NDK build:
|
||||
* Right click on ExoPlayerExt-VP9 in the Project Explorer pane and choose Properties
|
||||
* Click on C/C++ Build
|
||||
* Uncheck `Use default build command`
|
||||
* In `Build Command` enter: `ndk-build -j4` (adjust 4 to a reasonable number depending on the number of cores in your computer)
|
||||
* Click Apply
|
||||
|
||||
You can now create your own Android App project and add ExoPlayerLib along with ExoPlayerExt-VP9 as a dependencies to use ExoPlayer along with the VP9 Extension.
|
||||
|
||||
|
||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||
<!---
|
||||
Work around to point to two different links for the same text.
|
||||
-->
|
||||
[Android NDK ]: http://tools.android.com/recent/usingthendkplugin
|
||||
[Android SDK]: http://developer.android.com/sdk/installing/index.html?pkg=tools
|
||||
|
||||
## Building for various Architectures ##
|
||||
|
||||
### Android Studio ###
|
||||
|
||||
The manual invocation of `ndk-build` will build the library for all architectures and the correct one will be picked up from the APK based on the device its running on.
|
||||
|
||||
### Eclipse ###
|
||||
|
||||
libvpx is optimized for various architectures (like neon, x86, etc.). The `generate_libvpx_android_configs.sh` script generates Android configurations for the following architectures:
|
||||
|
||||
* armeabi (the default - does not include neon optimizations)
|
||||
* armeabi-v7a (choose this to enable neon optimizations)
|
||||
* mips
|
||||
* x86
|
||||
* all (will result in a larger binary but will cover all architectures)
|
||||
|
||||
You can build for a specific architecture in two ways:
|
||||
|
||||
* Method 1 (edit `Application.mk`)
|
||||
* Edit `${VP9_EXT_PATH}/jni/Application.mk` and add the following line `APP_ABI := <arch>` (where `<arch>` is one of the above 4 architectures)
|
||||
* Method 2 (pass NDK build flag)
|
||||
* Right click on ExoPlayerExt-VP9 in the Project Explorer pane and choose Properties
|
||||
* Click on C/C++ Build
|
||||
* Uncheck `Use default build command`
|
||||
* In `Build Command` enter: `ndk-build APP_ABI=<arch>` (where `<arch>` is one of the above 4 architectures)
|
||||
* Click Apply
|
||||
|
||||
## Other Things to Note ##
|
||||
|
||||
* Every time there is a change to the libvpx checkout:
|
||||
* Android config scripts should be re-generated by running `generate_libvpx_android_configs.sh`
|
||||
* Clean and re-build the project.
|
||||
* If you want to use your own version of libvpx or libyuv, place it in `${VP9_EXT_PATH}/jni/libvpx` or `${VP9_EXT_PATH}/jni/libyuv` respectively.
|
||||
|
45
extensions/vp9/build.gradle
Normal file
45
extensions/vp9/build.gradle
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2014 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 22
|
||||
buildToolsVersion "22.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 22
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
jniLibs.srcDir 'src/main/libs'
|
||||
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':library')
|
||||
}
|
||||
|
10
extensions/vp9/src/main/.classpath
Normal file
10
extensions/vp9/src/main/.classpath
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
|
||||
<classpathentry kind="src" path="java"/>
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="/ExoPlayerLib"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
57
extensions/vp9/src/main/.cproject
Normal file
57
extensions/vp9/src/main/.cproject
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
|
||||
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||
<cconfiguration id="com.android.toolchain.gcc.367693784">
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="com.android.toolchain.gcc.367693784" moduleId="org.eclipse.cdt.core.settings" name="Default">
|
||||
<externalSettings/>
|
||||
<extensions>
|
||||
<extension id="org.eclipse.cdt.core.VCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
</extensions>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<configuration artifactName="${ProjName}" buildProperties="" description="" id="com.android.toolchain.gcc.367693784" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
<folderInfo id="com.android.toolchain.gcc.367693784.1582606005" name="/" resourcePath="">
|
||||
<toolChain id="com.android.toolchain.gcc.2090539093" name="Android GCC" superClass="com.android.toolchain.gcc">
|
||||
<targetPlatform binaryParser="org.eclipse.cdt.core.ELF" id="com.android.targetPlatform.1021581688" isAbstract="false" superClass="com.android.targetPlatform"/>
|
||||
<builder buildPath="${workspace_loc:/ExoPlayerExt-VP9}/jni" id="com.android.builder.1955717109" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Android Builder" superClass="com.android.builder">
|
||||
<outputEntries>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="obj"/>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="outputPath" name="libs"/>
|
||||
</outputEntries>
|
||||
</builder>
|
||||
<tool id="com.android.gcc.compiler.162335776" name="Android GCC Compiler" superClass="com.android.gcc.compiler">
|
||||
<inputType id="com.android.gcc.inputType.78164988" superClass="com.android.gcc.inputType"/>
|
||||
</tool>
|
||||
</toolChain>
|
||||
</folderInfo>
|
||||
<sourceEntries>
|
||||
<entry flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name="jni"/>
|
||||
</sourceEntries>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
</cconfiguration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<project id="ExoPlayerExt-VP9.null.410683598" name="ExoPlayerExt-VP9"/>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
|
||||
<storageModule moduleId="scannerConfiguration">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
<scannerConfigBuildInfo instanceId="com.android.toolchain.gcc.367693784;com.android.toolchain.gcc.367693784.1582606005;com.android.gcc.compiler.162335776;com.android.gcc.inputType.78164988">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="com.android.AndroidPerProjectProfile"/>
|
||||
</scannerConfigBuildInfo>
|
||||
</storageModule>
|
||||
<storageModule moduleId="refreshScope" versionNumber="2">
|
||||
<configuration configurationName="Default">
|
||||
<resource resourceType="PROJECT" workspacePath="/ExoPlayerExt-VP9"/>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
</cproject>
|
97
extensions/vp9/src/main/.project
Normal file
97
extensions/vp9/src/main/.project
Normal file
@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>ExoPlayerExt-VP9</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
<dictionary>
|
||||
<key>?children?</key>
|
||||
<value>?name?=outputEntries\|?children?=?name?=entry\\\\\\\|\\\|?name?=entry\\\\\\\|\\\|\||</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>?name?</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.append_environment</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildArguments</key>
|
||||
<value></value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.buildCommand</key>
|
||||
<value>ndk-build</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.cleanBuildTarget</key>
|
||||
<value>clean</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.contents</key>
|
||||
<value>org.eclipse.cdt.make.core.activeConfigSettings</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableAutoBuild</key>
|
||||
<value>false</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableCleanBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.enableFullBuild</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.stopOnError</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
<dictionary>
|
||||
<key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key>
|
||||
<value>true</value>
|
||||
</dictionary>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
12
extensions/vp9/src/main/.settings/org.eclipse.jdt.core.prefs
Normal file
12
extensions/vp9/src/main/.settings/org.eclipse.jdt.core.prefs
Normal file
@ -0,0 +1,12 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
|
||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
||||
org.eclipse.jdt.core.compiler.compliance=1.7
|
||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
||||
org.eclipse.jdt.core.compiler.source=1.7
|
23
extensions/vp9/src/main/AndroidManifest.xml
Normal file
23
extensions/vp9/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2014 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer.ext.vp9">
|
||||
|
||||
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="22"/>
|
||||
<uses-feature android:glEsVersion="0x00020000"/>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,505 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.vp9;
|
||||
|
||||
import com.google.android.exoplayer.ExoPlaybackException;
|
||||
import com.google.android.exoplayer.ExoPlayer;
|
||||
import com.google.android.exoplayer.MediaFormat;
|
||||
import com.google.android.exoplayer.MediaFormatHolder;
|
||||
import com.google.android.exoplayer.SampleSource;
|
||||
import com.google.android.exoplayer.TrackRenderer;
|
||||
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.InputBuffer;
|
||||
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
|
||||
import com.google.android.exoplayer.util.MimeTypes;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.view.Surface;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Decodes and renders video using the native VP9 decoder.
|
||||
*/
|
||||
public class LibvpxVideoTrackRenderer extends TrackRenderer {
|
||||
|
||||
/**
|
||||
* Interface definition for a callback to be notified of {@link LibvpxVideoTrackRenderer} events.
|
||||
*/
|
||||
public interface EventListener {
|
||||
|
||||
/**
|
||||
* Invoked to report the number of frames dropped by the renderer. Dropped frames are reported
|
||||
* whenever the renderer is stopped having dropped frames, and optionally, whenever the count
|
||||
* reaches a specified threshold whilst the renderer is started.
|
||||
*
|
||||
* @param count The number of dropped frames.
|
||||
* @param elapsed The duration in milliseconds over which the frames were dropped. This
|
||||
* duration is timed from when the renderer was started or from when dropped frames were
|
||||
* last reported (whichever was more recent), and not from when the first of the reported
|
||||
* drops occurred.
|
||||
*/
|
||||
void onDroppedFrames(int count, long elapsed);
|
||||
|
||||
/**
|
||||
* Invoked each time there's a change in the size of the video being rendered.
|
||||
*
|
||||
* @param width The video width in pixels.
|
||||
* @param height The video height in pixels.
|
||||
*/
|
||||
void onVideoSizeChanged(int width, int height);
|
||||
|
||||
/**
|
||||
* Invoked when a frame is rendered to a surface for the first time following that surface
|
||||
* having been set as the target for the renderer.
|
||||
*
|
||||
* @param surface The surface to which a first frame has been rendered.
|
||||
*/
|
||||
void onDrawnToSurface(Surface surface);
|
||||
|
||||
/**
|
||||
* Invoked when one of the following happens: libvpx initialization failure, decoder error,
|
||||
* renderer error.
|
||||
*
|
||||
* @param e The corresponding exception.
|
||||
*/
|
||||
void onDecoderError(VpxDecoderException e);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of a message that can be passed to an instance of this class via
|
||||
* {@link ExoPlayer#sendMessage} or {@link ExoPlayer#blockingSendMessage}. The message object
|
||||
* should be the target {@link Surface}, or null.
|
||||
*/
|
||||
public static final int MSG_SET_SURFACE = 1;
|
||||
public static final int MSG_SET_VPX_SURFACE_VIEW = 2;
|
||||
|
||||
private final SampleSource source;
|
||||
private final boolean scaleToFit;
|
||||
private final Handler eventHandler;
|
||||
private final EventListener eventListener;
|
||||
private final int maxDroppedFrameCountToNotify;
|
||||
private final MediaFormatHolder formatHolder;
|
||||
|
||||
private MediaFormat format;
|
||||
private VpxDecoderWrapper decoder;
|
||||
private InputBuffer inputBuffer;
|
||||
private OutputBuffer outputBuffer;
|
||||
|
||||
private Bitmap bitmap;
|
||||
private boolean drawnToSurface;
|
||||
private boolean renderedFirstFrame;
|
||||
private Surface surface;
|
||||
private VpxVideoSurfaceView vpxVideoSurfaceView;
|
||||
private boolean outputRgb;
|
||||
|
||||
private int trackIndex;
|
||||
private long currentPositionUs;
|
||||
private boolean inputStreamEnded;
|
||||
private boolean outputStreamEnded;
|
||||
private boolean sourceIsReady;
|
||||
private int previousWidth;
|
||||
private int previousHeight;
|
||||
|
||||
private int droppedFrameCount;
|
||||
private long droppedFrameAccumulationStartTimeMs;
|
||||
|
||||
/**
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param scaleToFit Boolean that indicates if video frames should be scaled to fit when
|
||||
* rendering.
|
||||
*/
|
||||
public LibvpxVideoTrackRenderer(SampleSource source, boolean scaleToFit) {
|
||||
this(source, scaleToFit, null, null, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param source The upstream source from which the renderer obtains samples.
|
||||
* @param scaleToFit Boolean that indicates if video frames should be scaled to fit when
|
||||
* rendering.
|
||||
* @param eventHandler A handler to use when delivering events to {@code eventListener}. May be
|
||||
* null if delivery of events is not required.
|
||||
* @param eventListener A listener of events. May be null if delivery of events is not required.
|
||||
* @param maxDroppedFrameCountToNotify The maximum number of frames that can be dropped between
|
||||
* invocations of {@link EventListener#onDroppedFrames(int, long)}.
|
||||
*/
|
||||
public LibvpxVideoTrackRenderer(SampleSource source, boolean scaleToFit,
|
||||
Handler eventHandler, EventListener eventListener, int maxDroppedFrameCountToNotify) {
|
||||
this.source = source;
|
||||
this.scaleToFit = scaleToFit;
|
||||
this.eventHandler = eventHandler;
|
||||
this.eventListener = eventListener;
|
||||
this.maxDroppedFrameCountToNotify = maxDroppedFrameCountToNotify;
|
||||
previousWidth = -1;
|
||||
previousHeight = -1;
|
||||
formatHolder = new MediaFormatHolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int doPrepare(long positionUs) throws ExoPlaybackException {
|
||||
try {
|
||||
boolean sourcePrepared = source.prepare(positionUs);
|
||||
if (!sourcePrepared) {
|
||||
return TrackRenderer.STATE_UNPREPARED;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
|
||||
for (int i = 0; i < source.getTrackCount(); i++) {
|
||||
if (source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_VP9)
|
||||
|| source.getTrackInfo(i).mimeType.equalsIgnoreCase(MimeTypes.VIDEO_WEBM)) {
|
||||
trackIndex = i;
|
||||
return TrackRenderer.STATE_PREPARED;
|
||||
}
|
||||
}
|
||||
|
||||
return TrackRenderer.STATE_IGNORE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sourceIsReady = source.continueBuffering(positionUs);
|
||||
checkForDiscontinuity();
|
||||
if (format == null) {
|
||||
readFormat();
|
||||
} else {
|
||||
// TODO: Add support for dynamic switching between one type of surface to another.
|
||||
// Create the decoder.
|
||||
if (decoder == null) {
|
||||
decoder = new VpxDecoderWrapper(outputRgb);
|
||||
decoder.start();
|
||||
}
|
||||
processOutputBuffer(positionUs, elapsedRealtimeUs);
|
||||
|
||||
// Queue input buffers.
|
||||
while (feedInputBuffer()) {}
|
||||
}
|
||||
} catch (VpxDecoderException e) {
|
||||
notifyDecoderError(e);
|
||||
throw new ExoPlaybackException(e);
|
||||
} catch (IOException e) {
|
||||
throw new ExoPlaybackException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOutputBuffer(long positionUs, long elapsedRealtimeUs)
|
||||
throws VpxDecoderException {
|
||||
if (outputStreamEnded) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (outputBuffer == null) {
|
||||
outputBuffer = decoder.dequeueOutputBuffer();
|
||||
if (outputBuffer == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (outputBuffer.flags == VpxDecoderWrapper.FLAG_END_OF_STREAM) {
|
||||
outputStreamEnded = true;
|
||||
releaseOutputBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
long elapsedSinceStartOfLoop = SystemClock.elapsedRealtime() * 1000 - elapsedRealtimeUs;
|
||||
long timeToRenderUs = outputBuffer.timestampUs - positionUs - elapsedSinceStartOfLoop;
|
||||
|
||||
if (timeToRenderUs < -30000 || outputBuffer.timestampUs < currentPositionUs) {
|
||||
// Drop frame if we are too late.
|
||||
droppedFrameCount++;
|
||||
if (droppedFrameCount == maxDroppedFrameCountToNotify) {
|
||||
notifyAndResetDroppedFrameCount();
|
||||
}
|
||||
releaseOutputBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have not renderered any frame so far (either initially or immediately following a
|
||||
// seek), render one frame irresepective of the state.
|
||||
if (!renderedFirstFrame) {
|
||||
renderBuffer();
|
||||
renderedFirstFrame = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Do nothing if we are not playing or if we are too early to render the next frame.
|
||||
if (getState() != TrackRenderer.STATE_STARTED || timeToRenderUs > 30000) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (timeToRenderUs > 11000) {
|
||||
try {
|
||||
// Subtracting 10000 rather than 11000 ensures that the sleep time
|
||||
// will be at least 1ms.
|
||||
Thread.sleep((timeToRenderUs - 10000) / 1000);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
renderBuffer();
|
||||
}
|
||||
|
||||
private void renderBuffer() throws VpxDecoderException {
|
||||
notifyIfVideoSizeChanged(outputBuffer);
|
||||
if (outputRgb) {
|
||||
renderRgbFrame(outputBuffer, scaleToFit);
|
||||
} else {
|
||||
vpxVideoSurfaceView.renderFrame(outputBuffer);
|
||||
}
|
||||
if (!drawnToSurface) {
|
||||
drawnToSurface = true;
|
||||
notifyDrawnToSurface(surface);
|
||||
}
|
||||
releaseOutputBuffer();
|
||||
}
|
||||
|
||||
private void releaseOutputBuffer() throws VpxDecoderException {
|
||||
currentPositionUs = outputBuffer.timestampUs;
|
||||
decoder.releaseOutputBuffer(outputBuffer);
|
||||
outputBuffer = null;
|
||||
}
|
||||
|
||||
private void renderRgbFrame(OutputBuffer outputBuffer, boolean scale) {
|
||||
if (bitmap == null || bitmap.getWidth() != outputBuffer.width
|
||||
|| bitmap.getHeight() != outputBuffer.height) {
|
||||
bitmap = Bitmap.createBitmap(outputBuffer.width, outputBuffer.height, Bitmap.Config.RGB_565);
|
||||
}
|
||||
bitmap.copyPixelsFromBuffer(outputBuffer.data);
|
||||
Canvas canvas = surface.lockCanvas(null);
|
||||
if (scale) {
|
||||
canvas.scale(((float) canvas.getWidth()) / outputBuffer.width,
|
||||
((float) canvas.getHeight()) / outputBuffer.height);
|
||||
}
|
||||
canvas.drawBitmap(bitmap, 0, 0, null);
|
||||
surface.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
|
||||
private boolean feedInputBuffer() throws IOException, VpxDecoderException {
|
||||
if (inputStreamEnded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputBuffer == null) {
|
||||
inputBuffer = decoder.getInputBuffer();
|
||||
if (inputBuffer == null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int result = source.readData(trackIndex, currentPositionUs, formatHolder,
|
||||
inputBuffer.sampleHolder, false);
|
||||
if (result == SampleSource.NOTHING_READ) {
|
||||
return false;
|
||||
}
|
||||
if (result == SampleSource.DISCONTINUITY_READ) {
|
||||
flushDecoder();
|
||||
return true;
|
||||
}
|
||||
if (result == SampleSource.FORMAT_READ) {
|
||||
format = formatHolder.format;
|
||||
return true;
|
||||
}
|
||||
if (result == SampleSource.END_OF_STREAM) {
|
||||
inputBuffer.flags = VpxDecoderWrapper.FLAG_END_OF_STREAM;
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
inputBuffer = null;
|
||||
inputStreamEnded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
inputBuffer.width = format.width;
|
||||
inputBuffer.height = format.height;
|
||||
decoder.queueInputBuffer(inputBuffer);
|
||||
inputBuffer = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkForDiscontinuity() throws IOException {
|
||||
if (decoder == null) {
|
||||
return;
|
||||
}
|
||||
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, true);
|
||||
if (result == SampleSource.DISCONTINUITY_READ) {
|
||||
flushDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
private void flushDecoder() {
|
||||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
decoder.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnded() {
|
||||
return outputStreamEnded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isReady() {
|
||||
return format != null && sourceIsReady;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getDurationUs() {
|
||||
return source.getTrackInfo(trackIndex).durationUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getCurrentPositionUs() {
|
||||
return currentPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getBufferedPositionUs() {
|
||||
long sourceBufferedPosition = source.getBufferedPositionUs();
|
||||
return sourceBufferedPosition == UNKNOWN_TIME_US || sourceBufferedPosition == END_OF_TRACK_US
|
||||
? sourceBufferedPosition : Math.max(sourceBufferedPosition, getCurrentPositionUs());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void seekTo(long positionUs) throws ExoPlaybackException {
|
||||
currentPositionUs = positionUs;
|
||||
source.seekToUs(positionUs);
|
||||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
renderedFirstFrame = false;
|
||||
sourceIsReady = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabled(long positionUs, boolean joining) {
|
||||
source.enable(trackIndex, positionUs);
|
||||
sourceIsReady = false;
|
||||
inputStreamEnded = false;
|
||||
outputStreamEnded = false;
|
||||
renderedFirstFrame = false;
|
||||
currentPositionUs = positionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStarted() {
|
||||
droppedFrameCount = 0;
|
||||
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopped() {
|
||||
notifyAndResetDroppedFrameCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReleased() {
|
||||
source.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisabled() {
|
||||
if (decoder != null) {
|
||||
decoder.release();
|
||||
decoder = null;
|
||||
}
|
||||
inputBuffer = null;
|
||||
outputBuffer = null;
|
||||
format = null;
|
||||
source.disable(trackIndex);
|
||||
}
|
||||
|
||||
private void readFormat() throws IOException {
|
||||
int result = source.readData(trackIndex, currentPositionUs, formatHolder, null, false);
|
||||
if (result == SampleSource.FORMAT_READ) {
|
||||
format = formatHolder.format;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
|
||||
if (messageType == MSG_SET_SURFACE) {
|
||||
surface = (Surface) message;
|
||||
vpxVideoSurfaceView = null;
|
||||
outputRgb = true;
|
||||
} else if (messageType == MSG_SET_VPX_SURFACE_VIEW) {
|
||||
vpxVideoSurfaceView = (VpxVideoSurfaceView) message;
|
||||
surface = null;
|
||||
outputRgb = false;
|
||||
} else {
|
||||
super.handleMessage(messageType, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyIfVideoSizeChanged(final OutputBuffer outputBuffer) {
|
||||
if (previousWidth == -1 || previousHeight == -1
|
||||
|| previousWidth != outputBuffer.width || previousHeight != outputBuffer.height) {
|
||||
previousWidth = outputBuffer.width;
|
||||
previousHeight = outputBuffer.height;
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onVideoSizeChanged(outputBuffer.width, outputBuffer.height);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyAndResetDroppedFrameCount() {
|
||||
if (eventHandler != null && eventListener != null && droppedFrameCount > 0) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
final int countToNotify = droppedFrameCount;
|
||||
final long elapsedToNotify = now - droppedFrameAccumulationStartTimeMs;
|
||||
droppedFrameCount = 0;
|
||||
droppedFrameAccumulationStartTimeMs = now;
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onDroppedFrames(countToNotify, elapsedToNotify);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDrawnToSurface(final Surface surface) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onDrawnToSurface(surface);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDecoderError(final VpxDecoderException e) {
|
||||
if (eventHandler != null && eventListener != null) {
|
||||
eventHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
eventListener.onDecoderError(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.vp9;
|
||||
|
||||
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* JNI Wrapper for the libvpx VP9 decoder.
|
||||
*/
|
||||
/* package */ class VpxDecoder {
|
||||
|
||||
private final long vpxDecContext;
|
||||
|
||||
static {
|
||||
System.loadLibrary("vpx");
|
||||
System.loadLibrary("vpxJNI");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the VP9 Decoder.
|
||||
*
|
||||
* @throws VpxDecoderException if the decoder fails to initialize.
|
||||
*/
|
||||
public VpxDecoder() throws VpxDecoderException {
|
||||
vpxDecContext = vpxInit();
|
||||
if (vpxDecContext == 0) {
|
||||
throw new VpxDecoderException("libvpx initialization error: failed to initialize decoder");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a vp9 encoded frame and converts it to RGB565.
|
||||
*
|
||||
* @param encoded The encoded buffer.
|
||||
* @param size Size of the encoded buffer.
|
||||
* @param outputBuffer The buffer into which the decoded frame should be written.
|
||||
* @param outputRgb True if the buffer should be converted to RGB color format. False if YUV
|
||||
* format should be retained.
|
||||
* @return 0 on success with a frame to render. 1 on success without a frame to render.
|
||||
* @throws VpxDecoderException on decode failure.
|
||||
*/
|
||||
public int decode(ByteBuffer encoded, int size, OutputBuffer outputBuffer, boolean outputRgb)
|
||||
throws VpxDecoderException {
|
||||
if (vpxDecode(vpxDecContext, encoded, size) != 0) {
|
||||
throw new VpxDecoderException("libvpx decode error: " + vpxGetErrorMessage(vpxDecContext));
|
||||
}
|
||||
return vpxGetFrame(vpxDecContext, outputBuffer, outputRgb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the decoder.
|
||||
*/
|
||||
public void close() {
|
||||
vpxClose(vpxDecContext);
|
||||
}
|
||||
|
||||
private native long vpxInit();
|
||||
private native long vpxClose(long context);
|
||||
private native long vpxDecode(long context, ByteBuffer encoded, int length);
|
||||
private native int vpxGetFrame(long context, OutputBuffer outputBuffer, boolean outputRgb);
|
||||
private native String vpxGetErrorMessage(long context);
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.vp9;
|
||||
|
||||
/**
|
||||
* Thrown when a libvpx decoder error occurs.
|
||||
*/
|
||||
public class VpxDecoderException extends Exception {
|
||||
|
||||
public VpxDecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.vp9;
|
||||
|
||||
import com.google.android.exoplayer.SampleHolder;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Wraps {@link VpxDecoder}, exposing a higher level decoder interface.
|
||||
*/
|
||||
/* package */ class VpxDecoderWrapper extends Thread {
|
||||
|
||||
public static final int FLAG_END_OF_STREAM = 1;
|
||||
|
||||
private static final int INPUT_BUFFER_SIZE = 768 * 1024; // Value based on cs/SoftVpx.cpp.
|
||||
private static final int NUM_BUFFERS = 16;
|
||||
|
||||
private final Object lock;
|
||||
private final boolean outputRgb;
|
||||
|
||||
private final LinkedList<InputBuffer> queuedInputBuffers;
|
||||
private final LinkedList<OutputBuffer> queuedOutputBuffers;
|
||||
private final InputBuffer[] availableInputBuffers;
|
||||
private final OutputBuffer[] availableOutputBuffers;
|
||||
private int availableInputBufferCount;
|
||||
private int availableOutputBufferCount;
|
||||
|
||||
private boolean flushDecodedOutputBuffer;
|
||||
private boolean released;
|
||||
|
||||
private VpxDecoderException decoderException;
|
||||
|
||||
/**
|
||||
* @param outputRgb True if the decoded output is in RGB color format. False if it is in YUV
|
||||
* color format.
|
||||
*/
|
||||
public VpxDecoderWrapper(boolean outputRgb) {
|
||||
lock = new Object();
|
||||
this.outputRgb = outputRgb;
|
||||
queuedInputBuffers = new LinkedList<>();
|
||||
queuedOutputBuffers = new LinkedList<>();
|
||||
availableInputBuffers = new InputBuffer[NUM_BUFFERS];
|
||||
availableOutputBuffers = new OutputBuffer[NUM_BUFFERS];
|
||||
availableInputBufferCount = NUM_BUFFERS;
|
||||
availableOutputBufferCount = NUM_BUFFERS;
|
||||
for (int i = 0; i < NUM_BUFFERS; i++) {
|
||||
availableInputBuffers[i] = new InputBuffer();
|
||||
availableOutputBuffers[i] = new OutputBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
public InputBuffer getInputBuffer() throws VpxDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
if (availableInputBufferCount == 0) {
|
||||
return null;
|
||||
}
|
||||
InputBuffer inputBuffer = availableInputBuffers[--availableInputBufferCount];
|
||||
inputBuffer.flags = 0;
|
||||
inputBuffer.sampleHolder.data.clear();
|
||||
return inputBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
public void queueInputBuffer(InputBuffer inputBuffer) throws VpxDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
queuedInputBuffers.addLast(inputBuffer);
|
||||
maybeNotifyDecodeLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public OutputBuffer dequeueOutputBuffer() throws VpxDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
if (queuedOutputBuffers.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return queuedOutputBuffers.removeFirst();
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseOutputBuffer(OutputBuffer outputBuffer) throws VpxDecoderException {
|
||||
synchronized (lock) {
|
||||
maybeThrowDecoderError();
|
||||
availableOutputBuffers[availableOutputBufferCount++] = outputBuffer;
|
||||
maybeNotifyDecodeLoop();
|
||||
}
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
synchronized (lock) {
|
||||
flushDecodedOutputBuffer = true;
|
||||
while (!queuedInputBuffers.isEmpty()) {
|
||||
availableInputBuffers[availableInputBufferCount++] = queuedInputBuffers.removeFirst();
|
||||
}
|
||||
while (!queuedOutputBuffers.isEmpty()) {
|
||||
availableOutputBuffers[availableOutputBufferCount++] = queuedOutputBuffers.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void release() {
|
||||
synchronized (lock) {
|
||||
released = true;
|
||||
lock.notify();
|
||||
}
|
||||
try {
|
||||
join();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private void maybeThrowDecoderError() throws VpxDecoderException {
|
||||
if (decoderException != null) {
|
||||
throw decoderException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the decode loop if there exists a queued input buffer and an available output buffer
|
||||
* to decode into.
|
||||
* <p>
|
||||
* Should only be called whilst synchronized on the lock object.
|
||||
*/
|
||||
private void maybeNotifyDecodeLoop() {
|
||||
if (!queuedInputBuffers.isEmpty() && availableOutputBufferCount > 0) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
VpxDecoder decoder = null;
|
||||
try {
|
||||
decoder = new VpxDecoder();
|
||||
while (decodeBuffer(decoder)) {
|
||||
// Do nothing.
|
||||
}
|
||||
} catch (VpxDecoderException e) {
|
||||
synchronized (lock) {
|
||||
decoderException = e;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Shouldn't ever happen.
|
||||
} finally {
|
||||
if (decoder != null) {
|
||||
decoder.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean decodeBuffer(VpxDecoder decoder) throws InterruptedException,
|
||||
VpxDecoderException {
|
||||
InputBuffer inputBuffer;
|
||||
OutputBuffer outputBuffer;
|
||||
|
||||
// Wait until we have an input buffer to decode, and an output buffer to decode into.
|
||||
synchronized (lock) {
|
||||
while (!released && (queuedInputBuffers.isEmpty() || availableOutputBufferCount == 0)) {
|
||||
lock.wait();
|
||||
}
|
||||
if (released) {
|
||||
return false;
|
||||
}
|
||||
inputBuffer = queuedInputBuffers.removeFirst();
|
||||
outputBuffer = availableOutputBuffers[--availableOutputBufferCount];
|
||||
flushDecodedOutputBuffer = false;
|
||||
}
|
||||
|
||||
// Decode.
|
||||
int decodeResult = -1;
|
||||
if (inputBuffer.flags == FLAG_END_OF_STREAM) {
|
||||
outputBuffer.flags = FLAG_END_OF_STREAM;
|
||||
} else {
|
||||
SampleHolder sampleHolder = inputBuffer.sampleHolder;
|
||||
outputBuffer.timestampUs = sampleHolder.timeUs;
|
||||
sampleHolder.data.position(sampleHolder.data.position() - sampleHolder.size);
|
||||
decodeResult = decoder.decode(sampleHolder.data, sampleHolder.size, outputBuffer, outputRgb);
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
if (flushDecodedOutputBuffer
|
||||
|| inputBuffer.sampleHolder.isDecodeOnly()
|
||||
|| decodeResult == 1) {
|
||||
// In the following cases, we make the output buffer available again rather than queuing it
|
||||
// to be consumed:
|
||||
// 1) A flush occured whilst we were decoding.
|
||||
// 2) The input sample has decodeOnly flag set.
|
||||
// 3) The decode succeeded, but we did not get any frame back for rendering (happens in case
|
||||
// of an unpacked altref frame).
|
||||
availableOutputBuffers[availableOutputBufferCount++] = outputBuffer;
|
||||
} else {
|
||||
// Queue the decoded output buffer to be consumed.
|
||||
queuedOutputBuffers.addLast(outputBuffer);
|
||||
}
|
||||
// Make the input buffer available again.
|
||||
availableInputBuffers[availableInputBufferCount++] = inputBuffer;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* package */ static final class InputBuffer {
|
||||
|
||||
public final SampleHolder sampleHolder;
|
||||
|
||||
public int width;
|
||||
public int height;
|
||||
public int flags;
|
||||
|
||||
public InputBuffer() {
|
||||
sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||
sampleHolder.data = ByteBuffer.allocateDirect(INPUT_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* package */ static final class OutputBuffer {
|
||||
|
||||
public ByteBuffer data;
|
||||
public long timestampUs;
|
||||
public int width;
|
||||
public int height;
|
||||
public int flags;
|
||||
public ByteBuffer[] yuvPlanes;
|
||||
public int[] yuvStrides;
|
||||
|
||||
/**
|
||||
* This method is called from C++ through JNI after decoding is done. It will resize the
|
||||
* buffer based on the given dimensions.
|
||||
*/
|
||||
public void initForRgbFrame(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
int minimumRgbSize = width * height * 2;
|
||||
if (data == null || data.capacity() < minimumRgbSize) {
|
||||
data = ByteBuffer.allocateDirect(minimumRgbSize);
|
||||
yuvPlanes = null;
|
||||
}
|
||||
data.position(0);
|
||||
data.limit(minimumRgbSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from C++ through JNI after decoding is done. It will resize the
|
||||
* buffer based on the given stride.
|
||||
*/
|
||||
public void initForYuvFrame(int width, int height, int yStride, int uvStride) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
int yLength = yStride * height;
|
||||
int uvLength = uvStride * ((height + 1) / 2);
|
||||
int minimumYuvSize = yLength + (uvLength * 2);
|
||||
if (data == null || data.capacity() < minimumYuvSize) {
|
||||
data = ByteBuffer.allocateDirect(minimumYuvSize);
|
||||
}
|
||||
data.limit(minimumYuvSize);
|
||||
if (yuvPlanes == null) {
|
||||
yuvPlanes = new ByteBuffer[3];
|
||||
}
|
||||
// Rewrapping has to be done on every frame since the stride might have changed.
|
||||
data.position(0);
|
||||
yuvPlanes[0] = data.slice();
|
||||
yuvPlanes[0].limit(yLength);
|
||||
data.position(yLength);
|
||||
yuvPlanes[1] = data.slice();
|
||||
yuvPlanes[1].limit(uvLength);
|
||||
data.position(yLength + uvLength);
|
||||
yuvPlanes[2] = data.slice();
|
||||
yuvPlanes[2].limit(uvLength);
|
||||
if (yuvStrides == null) {
|
||||
yuvStrides = new int[3];
|
||||
}
|
||||
yuvStrides[0] = yStride;
|
||||
yuvStrides[1] = uvStride;
|
||||
yuvStrides[2] = uvStride;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.vp9;
|
||||
|
||||
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
|
||||
|
||||
import android.opengl.GLES20;
|
||||
import android.opengl.GLSurfaceView;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
* GLSurfaceView.Renderer implementation that can render YUV Frames returned by libvpx after
|
||||
* decoding. It does the YUV to RGB color conversion in the Fragment Shader.
|
||||
*/
|
||||
/* package */ class VpxRenderer implements GLSurfaceView.Renderer {
|
||||
|
||||
private static final String VERTEX_SHADER =
|
||||
"varying vec2 interp_tc;\n"
|
||||
+ "attribute vec4 in_pos;\n"
|
||||
+ "attribute vec2 in_tc;\n"
|
||||
+ "void main() {\n"
|
||||
+ " gl_Position = in_pos;\n"
|
||||
+ " interp_tc = in_tc;\n"
|
||||
+ "}\n";
|
||||
private static final String[] TEXTURE_UNIFORMS = {"y_tex", "u_tex", "v_tex"};
|
||||
private static final String FRAGMENT_SHADER =
|
||||
"precision mediump float;\n"
|
||||
+ "varying vec2 interp_tc;\n"
|
||||
+ "uniform sampler2D y_tex;\n"
|
||||
+ "uniform sampler2D u_tex;\n"
|
||||
+ "uniform sampler2D v_tex;\n"
|
||||
+ "void main() {\n"
|
||||
+ " float y = 1.164 * (texture2D(y_tex, interp_tc).r - 0.0625);\n"
|
||||
+ " float u = texture2D(u_tex, interp_tc).r - 0.5;\n"
|
||||
+ " float v = texture2D(v_tex, interp_tc).r - 0.5;\n"
|
||||
+ " gl_FragColor = vec4(y + 1.596 * v, "
|
||||
+ " y - 0.391 * u - 0.813 * v, "
|
||||
+ " y + 2.018 * u, "
|
||||
+ " 1.0);\n"
|
||||
+ "}\n";
|
||||
private static final FloatBuffer TEXTURE_VERTICES = nativeFloatBuffer(
|
||||
-1.0f, 1.0f,
|
||||
-1.0f, -1.0f,
|
||||
1.0f, 1.0f,
|
||||
1.0f, -1.0f);
|
||||
private final int[] yuvTextures = new int[3];
|
||||
|
||||
private int program;
|
||||
private int texLocation;
|
||||
private FloatBuffer textureCoords;
|
||||
private volatile OutputBuffer outputBuffer;
|
||||
private int previousWidth;
|
||||
private int previousStride;
|
||||
|
||||
public VpxRenderer() {
|
||||
previousWidth = -1;
|
||||
previousStride = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a frame to be rendered. This should be followed by a call to
|
||||
* VpxVideoSurfaceView.requestRender() to actually render the frame.
|
||||
*
|
||||
* @param outputBuffer OutputBuffer containing the YUV Frame to be rendered
|
||||
*/
|
||||
public void setFrame(OutputBuffer outputBuffer) {
|
||||
this.outputBuffer = outputBuffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
|
||||
// Create the GL program.
|
||||
program = GLES20.glCreateProgram();
|
||||
|
||||
// Add the vertex and fragment shaders.
|
||||
addShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER, program);
|
||||
addShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER, program);
|
||||
|
||||
// Link the GL program.
|
||||
GLES20.glLinkProgram(program);
|
||||
int[] result = new int[] {
|
||||
GLES20.GL_FALSE
|
||||
};
|
||||
result[0] = GLES20.GL_FALSE;
|
||||
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, result, 0);
|
||||
abortUnless(result[0] == GLES20.GL_TRUE, GLES20.glGetProgramInfoLog(program));
|
||||
GLES20.glUseProgram(program);
|
||||
int posLocation = GLES20.glGetAttribLocation(program, "in_pos");
|
||||
GLES20.glEnableVertexAttribArray(posLocation);
|
||||
GLES20.glVertexAttribPointer(
|
||||
posLocation, 2, GLES20.GL_FLOAT, false, 0, TEXTURE_VERTICES);
|
||||
texLocation = GLES20.glGetAttribLocation(program, "in_tc");
|
||||
GLES20.glEnableVertexAttribArray(texLocation);
|
||||
setupTextures();
|
||||
checkNoGLES2Error();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceChanged(GL10 unused, int width, int height) {
|
||||
GLES20.glViewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawFrame(GL10 unused) {
|
||||
OutputBuffer outputBuffer = this.outputBuffer;
|
||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
||||
if (outputBuffer == null) {
|
||||
// Nothing to render yet.
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int h = (i == 0) ? outputBuffer.height : (outputBuffer.height + 1) / 2;
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
|
||||
GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 1);
|
||||
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_LUMINANCE, outputBuffer.yuvStrides[i],
|
||||
h, 0, GLES20.GL_LUMINANCE, GLES20.GL_UNSIGNED_BYTE, outputBuffer.yuvPlanes[i]);
|
||||
}
|
||||
// Set cropping of stride if either width or stride has changed.
|
||||
if (previousWidth != outputBuffer.width || previousStride != outputBuffer.yuvStrides[0]) {
|
||||
float crop = (float) outputBuffer.width / outputBuffer.yuvStrides[0];
|
||||
textureCoords = nativeFloatBuffer(
|
||||
0.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
crop, 0.0f,
|
||||
crop, 1.0f);
|
||||
GLES20.glVertexAttribPointer(
|
||||
texLocation, 2, GLES20.GL_FLOAT, false, 0, textureCoords);
|
||||
previousWidth = outputBuffer.width;
|
||||
previousStride = outputBuffer.yuvStrides[0];
|
||||
}
|
||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
|
||||
checkNoGLES2Error();
|
||||
}
|
||||
|
||||
private void addShader(int type, String source, int program) {
|
||||
int[] result = new int[] {
|
||||
GLES20.GL_FALSE
|
||||
};
|
||||
int shader = GLES20.glCreateShader(type);
|
||||
GLES20.glShaderSource(shader, source);
|
||||
GLES20.glCompileShader(shader);
|
||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, 0);
|
||||
abortUnless(result[0] == GLES20.GL_TRUE,
|
||||
GLES20.glGetShaderInfoLog(shader) + ", source: " + source);
|
||||
GLES20.glAttachShader(program, shader);
|
||||
GLES20.glDeleteShader(shader);
|
||||
|
||||
checkNoGLES2Error();
|
||||
}
|
||||
|
||||
private void setupTextures() {
|
||||
GLES20.glGenTextures(3, yuvTextures, 0);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
GLES20.glUniform1i(GLES20.glGetUniformLocation(program, TEXTURE_UNIFORMS[i]), i);
|
||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + i);
|
||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, yuvTextures[i]);
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
|
||||
GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||
}
|
||||
checkNoGLES2Error();
|
||||
}
|
||||
|
||||
private void abortUnless(boolean condition, String msg) {
|
||||
if (!condition) {
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNoGLES2Error() {
|
||||
int error = GLES20.glGetError();
|
||||
if (error != GLES20.GL_NO_ERROR) {
|
||||
throw new RuntimeException("GLES20 error: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
private static FloatBuffer nativeFloatBuffer(float... array) {
|
||||
FloatBuffer buffer = ByteBuffer.allocateDirect(array.length * 4).order(
|
||||
ByteOrder.nativeOrder()).asFloatBuffer();
|
||||
buffer.put(array);
|
||||
buffer.flip();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer.ext.vp9;
|
||||
|
||||
import com.google.android.exoplayer.ext.vp9.VpxDecoderWrapper.OutputBuffer;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
/**
|
||||
* A GLSurfaceView extension that scales itself to the given aspect ratio.
|
||||
*/
|
||||
@TargetApi(11)
|
||||
public class VpxVideoSurfaceView extends GLSurfaceView {
|
||||
|
||||
private static final float MAX_ASPECT_RATIO_DEFORMATION_PERCENT = 0.01f;
|
||||
|
||||
private final VpxRenderer renderer;
|
||||
|
||||
private float videoAspectRatio;
|
||||
|
||||
public VpxVideoSurfaceView(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public VpxVideoSurfaceView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
renderer = new VpxRenderer();
|
||||
setPreserveEGLContextOnPause(true);
|
||||
setEGLContextClientVersion(2);
|
||||
setRenderer(renderer);
|
||||
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
|
||||
}
|
||||
|
||||
public void renderFrame(OutputBuffer outputBuffer) {
|
||||
renderer.setFrame(outputBuffer);
|
||||
requestRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the aspect ratio that this view should satisfy.
|
||||
*
|
||||
* @param widthHeightRatio The width to height ratio.
|
||||
*/
|
||||
public void setVideoWidthHeightRatio(float widthHeightRatio) {
|
||||
if (this.videoAspectRatio != widthHeightRatio) {
|
||||
this.videoAspectRatio = widthHeightRatio;
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int width = getMeasuredWidth();
|
||||
int height = getMeasuredHeight();
|
||||
if (videoAspectRatio != 0) {
|
||||
float viewAspectRatio = (float) width / height;
|
||||
float aspectDeformation = videoAspectRatio / viewAspectRatio - 1;
|
||||
if (aspectDeformation > MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
|
||||
height = (int) (width / videoAspectRatio);
|
||||
} else if (aspectDeformation < -MAX_ASPECT_RATIO_DEFORMATION_PERCENT) {
|
||||
width = (int) (height * videoAspectRatio);
|
||||
}
|
||||
}
|
||||
setMeasuredDimension(width, height);
|
||||
}
|
||||
|
||||
}
|
43
extensions/vp9/src/main/jni/Android.mk
Normal file
43
extensions/vp9/src/main/jni/Android.mk
Normal file
@ -0,0 +1,43 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
WORKING_DIR := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
APP_PLATFORM := android-10
|
||||
LIBVPX_ROOT := $(WORKING_DIR)/libvpx
|
||||
LIBYUV_ROOT := $(WORKING_DIR)/libyuv
|
||||
|
||||
# build libyuv_static.a
|
||||
LOCAL_PATH := $(WORKING_DIR)
|
||||
include $(LIBYUV_ROOT)/Android.mk
|
||||
|
||||
# build libvpx.so
|
||||
LOCAL_PATH := $(WORKING_DIR)
|
||||
include libvpx.mk
|
||||
|
||||
# build libvpxJNI.so
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_PATH := $(WORKING_DIR)
|
||||
LOCAL_MODULE := libvpxJNI
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_CPP_EXTENSION := .cc
|
||||
LOCAL_SRC_FILES := vpx_jni.cc
|
||||
LOCAL_LDLIBS := -llog -lz -lm
|
||||
LOCAL_SHARED_LIBRARIES := libvpx
|
||||
LOCAL_STATIC_LIBRARIES := libyuv_static cpufeatures
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
$(call import-module,android/cpufeatures)
|
19
extensions/vp9/src/main/jni/Application.mk
Normal file
19
extensions/vp9/src/main/jni/Application.mk
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
APP_OPTIM := release
|
||||
APP_STL := gnustl_static
|
||||
APP_CPPFLAGS := -frtti
|
106
extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh
Executable file
106
extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh
Executable file
@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
# a bash script that generates the necessary config files for libvpx android ndk
|
||||
# builds.
|
||||
|
||||
set -e
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: ${0} <path_to_android_ndk>"
|
||||
exit
|
||||
fi
|
||||
|
||||
ndk="${1}"
|
||||
shift 1
|
||||
|
||||
# configuration parameters common to all architectures
|
||||
common_params="--disable-examples --disable-docs --enable-realtime-only"
|
||||
common_params+=" --disable-vp8 --disable-vp9-encoder --disable-webm-io"
|
||||
common_params+=" --disable-libyuv --disable-runtime-cpu-detect"
|
||||
|
||||
# configuration parameters for various architectures
|
||||
arch[0]="armeabi-v7a"
|
||||
config[0]="--target=armv7-android-gcc --sdk-path=$ndk --enable-neon"
|
||||
config[0]+=" --enable-neon-asm"
|
||||
|
||||
arch[1]="armeabi"
|
||||
config[1]="--target=armv7-android-gcc --sdk-path=$ndk --disable-neon"
|
||||
config[1]+=" --disable-neon-asm"
|
||||
|
||||
arch[2]="mips"
|
||||
config[2]="--force-target=mips32-android-gcc --sdk-path=$ndk"
|
||||
|
||||
arch[3]="x86"
|
||||
config[3]="--force-target=x86-android-gcc --sdk-path=$ndk --disable-sse3"
|
||||
config[3]+=" --disable-ssse3 --disable-sse4_1 --disable-avx --disable-avx2"
|
||||
config[3]+=" --enable-pic"
|
||||
|
||||
limit=$((${#arch[@]} - 1))
|
||||
|
||||
# list of files allowed after running configure in each arch directory.
|
||||
# everything else will be removed.
|
||||
allowed_files="libvpx_srcs.txt vpx_config.c vpx_config.h vpx_scale_rtcd.h"
|
||||
allowed_files+=" vp8_rtcd.h vp9_rtcd.h vpx_version.h vpx_config.asm"
|
||||
|
||||
remove_trailing_whitespace() {
|
||||
sed -i 's/\s\+$//' "$@"
|
||||
}
|
||||
|
||||
convert_asm() {
|
||||
for i in $(seq 0 ${limit}); do
|
||||
while read file; do
|
||||
case "${file}" in
|
||||
*.asm.s)
|
||||
asm_file="libvpx/${file%.s}"
|
||||
cat "${asm_file}" | libvpx/build/make/ads2gas.pl > "libvpx/${file}"
|
||||
remove_trailing_whitespace "libvpx/${file}"
|
||||
rm "${asm_file}"
|
||||
;;
|
||||
esac
|
||||
done < libvpx_android_configs/${arch[${i}]}/libvpx_srcs.txt
|
||||
done
|
||||
}
|
||||
|
||||
extglob_status="$(shopt extglob | cut -f2)"
|
||||
shopt -s extglob
|
||||
for i in $(seq 0 ${limit}); do
|
||||
mkdir -p "libvpx_android_configs/${arch[${i}]}"
|
||||
cd "libvpx_android_configs/${arch[${i}]}"
|
||||
|
||||
# configure and make
|
||||
echo "build_android_configs: "
|
||||
echo "configure ${config[${i}]} ${common_params}"
|
||||
../../libvpx/configure ${config[${i}]} ${common_params}
|
||||
rm -f libvpx_srcs.txt
|
||||
make libvpx_srcs.txt
|
||||
|
||||
# remove files that aren't needed
|
||||
rm -rf !(${allowed_files// /|})
|
||||
remove_trailing_whitespace *
|
||||
|
||||
cd ../..
|
||||
done
|
||||
|
||||
# restore extglob status as it was before
|
||||
if [[ "${extglob_status}" == "off" ]]; then
|
||||
shopt -u extglob
|
||||
fi
|
||||
|
||||
convert_asm
|
||||
|
||||
echo "Generated android config files."
|
51
extensions/vp9/src/main/jni/libvpx.mk
Normal file
51
extensions/vp9/src/main/jni/libvpx.mk
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# Copyright (C) 2014 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
CONFIG_DIR := $(LOCAL_PATH)/libvpx_android_configs/$(TARGET_ARCH_ABI)
|
||||
libvpx_source_dir := $(LOCAL_PATH)/libvpx
|
||||
|
||||
LOCAL_MODULE := libvpx
|
||||
LOCAL_MODULE_CLASS := STATIC_LIBRARIES
|
||||
LOCAL_CFLAGS := -DHAVE_CONFIG_H=vpx_config.h
|
||||
LOCAL_ARM_MODE := arm
|
||||
LOCAL_CFLAGS += -O3
|
||||
|
||||
# config specific include should go first to pick up the config specific rtcd.
|
||||
LOCAL_C_INCLUDES := $(CONFIG_DIR) $(libvpx_source_dir)
|
||||
|
||||
# generate source file list
|
||||
libvpx_codec_srcs := $(sort $(shell cat $(CONFIG_DIR)/libvpx_srcs.txt))
|
||||
LOCAL_SRC_FILES := libvpx_android_configs/$(TARGET_ARCH_ABI)/vpx_config.c
|
||||
LOCAL_SRC_FILES += $(addprefix libvpx/, $(filter-out vpx_config.c, \
|
||||
$(filter %.c, $(libvpx_codec_srcs))))
|
||||
|
||||
# include assembly files if they exist
|
||||
# "%.asm.s" covers neon assembly and "%.asm" covers x86 assembly
|
||||
LOCAL_SRC_FILES += $(addprefix libvpx/, \
|
||||
$(filter %.asm.s %.asm, $(libvpx_codec_srcs)))
|
||||
|
||||
ifneq ($(findstring armeabi-v7a, $(TARGET_ARCH_ABI)),)
|
||||
# append .neon to *_neon.c and *.s
|
||||
LOCAL_SRC_FILES := $(subst _neon.c,_neon.c.neon,$(LOCAL_SRC_FILES))
|
||||
LOCAL_SRC_FILES := $(subst .s,.s.neon,$(LOCAL_SRC_FILES))
|
||||
endif
|
||||
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/libvpx \
|
||||
$(LOCAL_PATH)/libvpx/vpx
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
149
extensions/vp9/src/main/jni/vpx_jni.cc
Normal file
149
extensions/vp9/src/main/jni/vpx_jni.cc
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <cpu-features.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <new>
|
||||
|
||||
#include "libyuv.h" // NOLINT
|
||||
|
||||
#define VPX_CODEC_DISABLE_COMPAT 1
|
||||
#include "vpx/vpx_decoder.h"
|
||||
#include "vpx/vp8dx.h"
|
||||
|
||||
#define LOG_TAG "LIBVPX_DEC"
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \
|
||||
__VA_ARGS__))
|
||||
|
||||
#define FUNC(RETURN_TYPE, NAME, ...) \
|
||||
extern "C" { \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_vp9_VpxDecoder_ ## NAME \
|
||||
(JNIEnv* env, jobject thiz, ##__VA_ARGS__);\
|
||||
} \
|
||||
JNIEXPORT RETURN_TYPE \
|
||||
Java_com_google_android_exoplayer_ext_vp9_VpxDecoder_ ## NAME \
|
||||
(JNIEnv* env, jobject thiz, ##__VA_ARGS__)\
|
||||
|
||||
// JNI references for OutputBuffer class.
|
||||
static jmethodID initForRgbFrame;
|
||||
static jmethodID initForYuvFrame;
|
||||
static jfieldID dataField;
|
||||
|
||||
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
||||
JNIEnv* env;
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
FUNC(jlong, vpxInit) {
|
||||
vpx_codec_ctx_t* context = new vpx_codec_ctx_t();
|
||||
vpx_codec_dec_cfg_t cfg = {0};
|
||||
cfg.threads = android_getCpuCount();
|
||||
if (vpx_codec_dec_init(context, &vpx_codec_vp9_dx_algo, &cfg, 0)) {
|
||||
LOGE("ERROR: Fail to initialize libvpx decoder.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Populate JNI References.
|
||||
const jclass outputBufferClass = env->FindClass(
|
||||
"com/google/android/exoplayer/ext/vp9/VpxDecoderWrapper$OutputBuffer");
|
||||
initForYuvFrame = env->GetMethodID(outputBufferClass, "initForYuvFrame",
|
||||
"(IIII)V");
|
||||
initForRgbFrame = env->GetMethodID(outputBufferClass, "initForRgbFrame",
|
||||
"(II)V");
|
||||
dataField = env->GetFieldID(outputBufferClass, "data",
|
||||
"Ljava/nio/ByteBuffer;");
|
||||
|
||||
return reinterpret_cast<intptr_t>(context);
|
||||
}
|
||||
|
||||
FUNC(jlong, vpxDecode, jlong jContext, jobject encoded, jint len) {
|
||||
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
|
||||
const uint8_t* const buffer =
|
||||
reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(encoded));
|
||||
const vpx_codec_err_t status =
|
||||
vpx_codec_decode(context, buffer, len, NULL, 0);
|
||||
if (status != VPX_CODEC_OK) {
|
||||
LOGE("ERROR: vpx_codec_decode() failed, status= %d", status);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FUNC(jlong, vpxClose, jlong jContext) {
|
||||
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
|
||||
vpx_codec_destroy(context);
|
||||
delete context;
|
||||
return 0;
|
||||
}
|
||||
|
||||
FUNC(jint, vpxGetFrame, jlong jContext, jobject jOutputBuffer, jboolean isRGB) {
|
||||
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
|
||||
vpx_codec_iter_t iter = NULL;
|
||||
const vpx_image_t* const img = vpx_codec_get_frame(context, &iter);
|
||||
|
||||
if (img == NULL) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isRGB == JNI_TRUE) {
|
||||
// resize buffer if required.
|
||||
env->CallVoidMethod(jOutputBuffer, initForRgbFrame, img->d_w, img->d_h);
|
||||
|
||||
// get pointer to the data buffer.
|
||||
const jobject dataObject = env->GetObjectField(jOutputBuffer, dataField);
|
||||
uint8_t* const dst =
|
||||
reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(dataObject));
|
||||
|
||||
libyuv::I420ToRGB565(img->planes[VPX_PLANE_Y], img->stride[VPX_PLANE_Y],
|
||||
img->planes[VPX_PLANE_U], img->stride[VPX_PLANE_U],
|
||||
img->planes[VPX_PLANE_V], img->stride[VPX_PLANE_V],
|
||||
dst, img->d_w * 2, img->d_w, img->d_h);
|
||||
} else {
|
||||
// resize buffer if required.
|
||||
env->CallVoidMethod(jOutputBuffer, initForYuvFrame, img->d_w, img->d_h,
|
||||
img->stride[VPX_PLANE_Y], img->stride[VPX_PLANE_U]);
|
||||
|
||||
// get pointer to the data buffer.
|
||||
const jobject dataObject = env->GetObjectField(jOutputBuffer, dataField);
|
||||
jbyte* const data =
|
||||
reinterpret_cast<jbyte*>(env->GetDirectBufferAddress(dataObject));
|
||||
|
||||
// TODO: This copy can be eliminated by using external frame buffers. NOLINT
|
||||
// This is insignificant for smaller videos but takes ~1.5ms for 1080p
|
||||
// clips. So this should eventually be gotten rid of.
|
||||
const uint64_t y_length = img->stride[VPX_PLANE_Y] * img->d_h;
|
||||
const uint64_t uv_length = img->stride[VPX_PLANE_U] * ((img->d_h + 1) / 2);
|
||||
memcpy(data, img->planes[VPX_PLANE_Y], y_length);
|
||||
memcpy(data + y_length, img->planes[VPX_PLANE_U], uv_length);
|
||||
memcpy(data + y_length + uv_length, img->planes[VPX_PLANE_V], uv_length);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
FUNC(jstring, vpxGetErrorMessage, jlong jContext) {
|
||||
vpx_codec_ctx_t* const context = reinterpret_cast<vpx_codec_ctx_t*>(jContext);
|
||||
return env->NewStringUTF(vpx_codec_error(context));
|
||||
}
|
16
extensions/vp9/src/main/project.properties
Normal file
16
extensions/vp9/src/main/project.properties
Normal file
@ -0,0 +1,16 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system edit
|
||||
# "ant.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
#
|
||||
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
|
||||
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
|
||||
|
||||
# Project target.
|
||||
target=android-22
|
||||
android.library=true
|
||||
android.library.reference.1=../../../../library/src/main
|
2
extensions/vp9/src/main/res/.README.txt
Normal file
2
extensions/vp9/src/main/res/.README.txt
Normal file
@ -0,0 +1,2 @@
|
||||
This file is needed to make sure the res directory is present.
|
||||
The file is ignored by the Android toolchain because its name starts with a dot.
|
Loading…
x
Reference in New Issue
Block a user