--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -191,17 +191,18 @@ public:
// Register native methods.
JavaCallbacksSupport::Init();
mJavaCallbacks = CodecProxy::NativeCallbacks::New();
JavaCallbacksSupport::AttachNative(
mJavaCallbacks, mozilla::MakeUnique<CallbacksSupport>(this));
- mJavaDecoder = CodecProxy::Create(mFormat,
+ mJavaDecoder = CodecProxy::Create(false, // false indicates to create a decoder and true denotes encoder
+ mFormat,
mSurfaceTexture->JavaSurface(),
mJavaCallbacks,
mDrmStubId);
if (mJavaDecoder == nullptr) {
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
__func__);
}
mIsCodecSupportAdaptivePlayback =
@@ -271,17 +272,17 @@ public:
// Register native methods.
JavaCallbacksSupport::Init();
mJavaCallbacks = CodecProxy::NativeCallbacks::New();
JavaCallbacksSupport::AttachNative(
mJavaCallbacks, mozilla::MakeUnique<CallbacksSupport>(this));
mJavaDecoder =
- CodecProxy::Create(mFormat, nullptr, mJavaCallbacks, mDrmStubId);
+ CodecProxy::Create(false, mFormat, nullptr, mJavaCallbacks, mDrmStubId);
if (mJavaDecoder == nullptr) {
return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
__func__);
}
return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
}
--- a/mobile/android/base/aidl/org/mozilla/gecko/media/ICodec.aidl
+++ b/mobile/android/base/aidl/org/mozilla/gecko/media/ICodec.aidl
@@ -19,9 +19,10 @@ interface ICodec {
void stop();
void flush();
void release();
Sample dequeueInput(int size);
oneway void queueInput(in Sample sample);
oneway void releaseOutput(in Sample sample, in boolean render);
+ oneway void setRates(in int newBitRate);
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
@@ -30,11 +30,12 @@ public interface AsyncCodec {
public abstract void stop();
public abstract void flush();
// Must be called after flush().
public abstract void resumeReceivingInputs();
public abstract void release();
public abstract ByteBuffer getInputBuffer(int index);
public abstract ByteBuffer getOutputBuffer(int index);
public abstract void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
+ public abstract void setRates(int newBitRate);
public abstract void queueSecureInputBuffer(int index, int offset, CryptoInfo info, long presentationTimeUs, int flags);
public abstract void releaseOutputBuffer(int index, boolean render);
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
@@ -334,17 +334,17 @@ import java.util.concurrent.ConcurrentLi
if (mCodec != null) {
if (DEBUG) { Log.d(LOGTAG, "release existing codec: " + mCodec); }
releaseCodec();
}
if (DEBUG) { Log.d(LOGTAG, "configure " + this); }
MediaFormat fmt = format.asFormat();
- String codecName = getDecoderForFormat(fmt);
+ String codecName = getCodecForFormat(fmt, flags == MediaCodec.CONFIGURE_FLAG_ENCODE ? true : false);
if (codecName == null) {
Log.e(LOGTAG, "FAIL: cannot find codec");
return false;
}
try {
AsyncCodec codec = AsyncCodecFactory.create(codecName);
@@ -396,25 +396,25 @@ import java.util.concurrent.ConcurrentLi
mCodec.release();
} catch (Exception e) {
reportError(Error.FATAL, e);
}
mCodec = null;
}
- private String getDecoderForFormat(MediaFormat format) {
+ private String getCodecForFormat(MediaFormat format, boolean isEncoder) {
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime == null) {
return null;
}
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
- if (info.isEncoder()) {
+ if (info.isEncoder() == !isEncoder) {
continue;
}
String[] types = info.getSupportedTypes();
for (String t : types) {
if (t.equalsIgnoreCase(mime)) {
return info.getName();
}
}
@@ -489,16 +489,25 @@ import java.util.concurrent.ConcurrentLi
}
@Override
public synchronized void queueInput(Sample sample) throws RemoteException {
mInputProcessor.onSample(sample);
}
@Override
+ public synchronized void setRates(int newBitRate) {
+ try {
+ mCodec.setRates(newBitRate);
+ } catch (Exception e) {
+ reportError(Error.FATAL, e);
+ }
+ }
+
+ @Override
public synchronized void releaseOutput(Sample sample, boolean render) {
try {
mOutputProcessor.onRelease(sample, render);
} catch (Exception e) {
reportError(Error.FATAL, e);
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
@@ -22,16 +22,17 @@ import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
// Proxy class of ICodec binder.
public final class CodecProxy {
private static final String LOGTAG = "GeckoRemoteCodecProxy";
private static final boolean DEBUG = false;
private ICodec mRemote;
+ private boolean mIsEncoder;
private FormatParam mFormat;
private Surface mOutputSurface;
private CallbacksForwarder mCallbacks;
private String mRemoteDrmStubId;
private Queue<Sample> mSurfaceOutputs = new ConcurrentLinkedQueue<>();
public interface Callbacks {
void onInputExhausted();
@@ -97,41 +98,44 @@ public final class CodecProxy {
}
private void setEndOfInput(boolean end) {
mEndOfInput = end;
}
}
@WrapForJNI
- public static CodecProxy create(MediaFormat format,
+ public static CodecProxy create(boolean isEncoder,
+ MediaFormat format,
Surface surface,
Callbacks callbacks,
String drmStubId) {
- return RemoteManager.getInstance().createCodec(format, surface, callbacks, drmStubId);
+ return RemoteManager.getInstance().createCodec(isEncoder, format, surface, callbacks, drmStubId);
}
- public static CodecProxy createCodecProxy(MediaFormat format,
+ public static CodecProxy createCodecProxy(boolean isEncoder,
+ MediaFormat format,
Surface surface,
Callbacks callbacks,
String drmStubId) {
- return new CodecProxy(format, surface, callbacks, drmStubId);
+ return new CodecProxy(isEncoder, format, surface, callbacks, drmStubId);
}
- private CodecProxy(MediaFormat format, Surface surface, Callbacks callbacks, String drmStubId) {
+ private CodecProxy(boolean isEncoder, MediaFormat format, Surface surface, Callbacks callbacks, String drmStubId) {
+ mIsEncoder = isEncoder;
mFormat = new FormatParam(format);
mOutputSurface = surface;
mRemoteDrmStubId = drmStubId;
mCallbacks = new CallbacksForwarder(callbacks);
}
boolean init(ICodec remote) {
try {
remote.setCallbacks(mCallbacks);
- if (!remote.configure(mFormat, mOutputSurface, 0, mRemoteDrmStubId)) {
+ if (!remote.configure(mFormat, mOutputSurface, mIsEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, mRemoteDrmStubId)) {
return false;
}
remote.start();
} catch (RemoteException e) {
e.printStackTrace();
return false;
}
@@ -253,16 +257,42 @@ public final class CodecProxy {
} catch (RemoteException e) {
e.printStackTrace();
return false;
}
return true;
}
@WrapForJNI
+ public synchronized boolean setRates(int newBitRate) {
+ if (!mIsEncoder) {
+ Log.w(LOGTAG, "this api is encoder-only");
+ return false;
+ }
+
+ if (android.os.Build.VERSION.SDK_INT < 19) {
+ Log.w(LOGTAG, "this api was added in API level 19");
+ return false;
+ }
+
+ if (mRemote == null) {
+ Log.w(LOGTAG, "codec already ended");
+ return true;
+ }
+
+ try {
+ mRemote.setRates(newBitRate);
+ } catch (RemoteException e) {
+ Log.e(LOGTAG, "remote fail to set rates:" + newBitRate);
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ @WrapForJNI
public synchronized boolean releaseOutput(Sample sample, boolean render) {
if (!mSurfaceOutputs.remove(sample)) {
if (mRemote != null) Log.w(LOGTAG, "already released: " + sample);
return true;
}
if (mRemote == null) {
Log.w(LOGTAG, "codec already ended");
--- a/mobile/android/base/java/org/mozilla/gecko/media/FormatParam.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/FormatParam.java
@@ -14,16 +14,21 @@ import java.nio.ByteBuffer;
/** A wrapper to make {@link MediaFormat} parcelable.
* Supports following keys:
* <ul>
* <li>{@link MediaFormat#KEY_MIME}</li>
* <li>{@link MediaFormat#KEY_WIDTH}</li>
* <li>{@link MediaFormat#KEY_HEIGHT}</li>
* <li>{@link MediaFormat#KEY_CHANNEL_COUNT}</li>
* <li>{@link MediaFormat#KEY_SAMPLE_RATE}</li>
+ * <li>{@link MediaFormat#KEY_BIT_RATE}</li>
+ * <li>{@link MediaFormat#KEY_BITRATE_MODE}</li>
+ * <li>{@link MediaFormat#KEY_COLOR_FORMAT}</li>
+ * <li>{@link MediaFormat#KEY_FRAME_RATE}</li>
+ * <li>{@link MediaFormat#KEY_I_FRAME_INTERVAL}</li>
* <li>"csd-0"</li>
* <li>"csd-1"</li>
* </ul>
*/
public final class FormatParam implements Parcelable {
// Keys for codec specific config bits not exposed in {@link MediaFormat}.
private static final String KEY_CONFIG_0 = "csd-0";
private static final String KEY_CONFIG_1 = "csd-1";
@@ -89,16 +94,36 @@ public final class FormatParam implement
if (bundle.containsKey(KEY_CONFIG_0)) {
mFormat.setByteBuffer(KEY_CONFIG_0,
ByteBuffer.wrap(bundle.getByteArray(KEY_CONFIG_0)));
}
if (bundle.containsKey(KEY_CONFIG_1)) {
mFormat.setByteBuffer(KEY_CONFIG_1,
ByteBuffer.wrap(bundle.getByteArray((KEY_CONFIG_1))));
}
+ if (bundle.containsKey(MediaFormat.KEY_BIT_RATE)) {
+ mFormat.setInteger(MediaFormat.KEY_BIT_RATE,
+ bundle.getInt(MediaFormat.KEY_BIT_RATE));
+ }
+ if (bundle.containsKey(MediaFormat.KEY_BITRATE_MODE)) {
+ mFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
+ bundle.getInt(MediaFormat.KEY_BITRATE_MODE));
+ }
+ if (bundle.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
+ mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+ bundle.getInt(MediaFormat.KEY_COLOR_FORMAT));
+ }
+ if (bundle.containsKey(MediaFormat.KEY_FRAME_RATE)) {
+ mFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
+ bundle.getInt(MediaFormat.KEY_FRAME_RATE));
+ }
+ if (bundle.containsKey(MediaFormat.KEY_I_FRAME_INTERVAL)) {
+ mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
+ bundle.getInt(MediaFormat.KEY_I_FRAME_INTERVAL));
+ }
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeBundle(toBundle());
}
private Bundle toBundle() {
@@ -123,11 +148,26 @@ public final class FormatParam implement
bundle.putByteArray(KEY_CONFIG_0,
Sample.byteArrayFromBuffer(bytes, 0, bytes.capacity()));
}
if (mFormat.containsKey(KEY_CONFIG_1)) {
ByteBuffer bytes = mFormat.getByteBuffer(KEY_CONFIG_1);
bundle.putByteArray(KEY_CONFIG_1,
Sample.byteArrayFromBuffer(bytes, 0, bytes.capacity()));
}
+ if (mFormat.containsKey(MediaFormat.KEY_BIT_RATE)) {
+ bundle.putInt(MediaFormat.KEY_BIT_RATE, mFormat.getInteger(MediaFormat.KEY_BIT_RATE));
+ }
+ if (mFormat.containsKey(MediaFormat.KEY_BITRATE_MODE)) {
+ bundle.putInt(MediaFormat.KEY_BITRATE_MODE, mFormat.getInteger(MediaFormat.KEY_BITRATE_MODE));
+ }
+ if (mFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
+ bundle.putInt(MediaFormat.KEY_COLOR_FORMAT, mFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT));
+ }
+ if (mFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
+ bundle.putInt(MediaFormat.KEY_FRAME_RATE, mFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
+ }
+ if (mFormat.containsKey(MediaFormat.KEY_I_FRAME_INTERVAL)) {
+ bundle.putInt(MediaFormat.KEY_I_FRAME_INTERVAL, mFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL));
+ }
return bundle;
}
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
@@ -8,16 +8,17 @@ import org.mozilla.gecko.util.HardwareCo
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
+import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
// Implement async API using MediaCodec sync mode (API v16).
// This class uses internal worker thread/handler (mBufferPoller) to poll
@@ -328,21 +329,36 @@ final class JellyBeanAsyncCodec implemen
@Override
public void resumeReceivingInputs() {
for (int i = 0; i < mInputBuffers.length; i++) {
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
}
}
@Override
+ public final void setRates(int newBitRate) {
+ if (android.os.Build.VERSION.SDK_INT >= 19) {
+ Bundle params = new Bundle();
+ params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitRate * 1000);
+ mCodec.setParameters(params);
+ }
+ }
+
+ @Override
public final void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags) {
assertCallbacks();
mInputEnded = (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+ if ((android.os.Build.VERSION.SDK_INT >= 19) && ((flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0)) {
+ Bundle params = new Bundle();
+ params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+ mCodec.setParameters(params);
+ }
+
try {
mCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
} catch (IllegalStateException e) {
e.printStackTrace();
mCallbackSender.notifyError(ERROR_CODEC);
return;
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteManager.java
@@ -104,27 +104,28 @@ public final class RemoteManager impleme
if (mRemote != null) {
return true;
}
if (DEBUG) Log.d(LOGTAG, "init remote manager " + this);
return mConnection.connect();
}
- public synchronized CodecProxy createCodec(MediaFormat format,
+ public synchronized CodecProxy createCodec(boolean isEncoder,
+ MediaFormat format,
Surface surface,
CodecProxy.Callbacks callbacks,
String drmStubId) {
if (mRemote == null) {
if (DEBUG) Log.d(LOGTAG, "createCodec failed due to not initialize");
return null;
}
try {
ICodec remote = mRemote.createCodec();
- CodecProxy proxy = CodecProxy.createCodecProxy(format, surface, callbacks, drmStubId);
+ CodecProxy proxy = CodecProxy.createCodecProxy(isEncoder, format, surface, callbacks, drmStubId);
if (proxy.init(remote)) {
mProxies.add(proxy);
return proxy;
} else {
return null;
}
} catch (RemoteException e) {
e.printStackTrace();
--- a/widget/android/fennec/FennecJNIWrappers.cpp
+++ b/widget/android/fennec/FennecJNIWrappers.cpp
@@ -179,19 +179,19 @@ auto AudioFocusAgent::NotifyStoppedPlayi
}
const char CodecProxy::name[] =
"org/mozilla/gecko/media/CodecProxy";
constexpr char CodecProxy::Create_t::name[];
constexpr char CodecProxy::Create_t::signature[];
-auto CodecProxy::Create(mozilla::jni::Object::Param a0, mozilla::jni::Object::Param a1, mozilla::jni::Object::Param a2, mozilla::jni::String::Param a3) -> CodecProxy::LocalRef
+auto CodecProxy::Create(bool a0, mozilla::jni::Object::Param a1, mozilla::jni::Object::Param a2, mozilla::jni::Object::Param a3, mozilla::jni::String::Param a4) -> CodecProxy::LocalRef
{
- return mozilla::jni::Method<Create_t>::Call(CodecProxy::Context(), nullptr, a0, a1, a2, a3);
+ return mozilla::jni::Method<Create_t>::Call(CodecProxy::Context(), nullptr, a0, a1, a2, a3, a4);
}
constexpr char CodecProxy::Flush_t::name[];
constexpr char CodecProxy::Flush_t::signature[];
auto CodecProxy::Flush() const -> bool
{
return mozilla::jni::Method<Flush_t>::Call(CodecProxy::mCtx, nullptr);
@@ -224,16 +224,24 @@ auto CodecProxy::Release() const -> bool
constexpr char CodecProxy::ReleaseOutput_t::name[];
constexpr char CodecProxy::ReleaseOutput_t::signature[];
auto CodecProxy::ReleaseOutput(mozilla::jni::Object::Param a0, bool a1) const -> bool
{
return mozilla::jni::Method<ReleaseOutput_t>::Call(CodecProxy::mCtx, nullptr, a0, a1);
}
+constexpr char CodecProxy::SetRates_t::name[];
+constexpr char CodecProxy::SetRates_t::signature[];
+
+auto CodecProxy::SetRates(int32_t a0) const -> bool
+{
+ return mozilla::jni::Method<SetRates_t>::Call(CodecProxy::mCtx, nullptr, a0);
+}
+
const char CodecProxy::NativeCallbacks::name[] =
"org/mozilla/gecko/media/CodecProxy$NativeCallbacks";
constexpr char CodecProxy::NativeCallbacks::New_t::name[];
constexpr char CodecProxy::NativeCallbacks::New_t::signature[];
auto CodecProxy::NativeCallbacks::New() -> NativeCallbacks::LocalRef
{
--- a/widget/android/fennec/FennecJNIWrappers.h
+++ b/widget/android/fennec/FennecJNIWrappers.h
@@ -663,33 +663,34 @@ public:
class NativeCallbacks;
struct Create_t {
typedef CodecProxy Owner;
typedef CodecProxy::LocalRef ReturnType;
typedef CodecProxy::Param SetterType;
typedef mozilla::jni::Args<
+ bool,
mozilla::jni::Object::Param,
mozilla::jni::Object::Param,
mozilla::jni::Object::Param,
mozilla::jni::String::Param> Args;
static constexpr char name[] = "create";
static constexpr char signature[] =
- "(Landroid/media/MediaFormat;Landroid/view/Surface;Lorg/mozilla/gecko/media/CodecProxy$Callbacks;Ljava/lang/String;)Lorg/mozilla/gecko/media/CodecProxy;";
+ "(ZLandroid/media/MediaFormat;Landroid/view/Surface;Lorg/mozilla/gecko/media/CodecProxy$Callbacks;Ljava/lang/String;)Lorg/mozilla/gecko/media/CodecProxy;";
static const bool isStatic = true;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::ANY;
static const mozilla::jni::DispatchTarget dispatchTarget =
mozilla::jni::DispatchTarget::CURRENT;
};
- static auto Create(mozilla::jni::Object::Param, mozilla::jni::Object::Param, mozilla::jni::Object::Param, mozilla::jni::String::Param) -> CodecProxy::LocalRef;
+ static auto Create(bool, mozilla::jni::Object::Param, mozilla::jni::Object::Param, mozilla::jni::Object::Param, mozilla::jni::String::Param) -> CodecProxy::LocalRef;
struct Flush_t {
typedef CodecProxy Owner;
typedef bool ReturnType;
typedef bool SetterType;
typedef mozilla::jni::Args<> Args;
static constexpr char name[] = "flush";
static constexpr char signature[] =
@@ -781,16 +782,36 @@ public:
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::ANY;
static const mozilla::jni::DispatchTarget dispatchTarget =
mozilla::jni::DispatchTarget::CURRENT;
};
auto ReleaseOutput(mozilla::jni::Object::Param, bool) const -> bool;
+ struct SetRates_t {
+ typedef CodecProxy Owner;
+ typedef bool ReturnType;
+ typedef bool SetterType;
+ typedef mozilla::jni::Args<
+ int32_t> Args;
+ static constexpr char name[] = "setRates";
+ static constexpr char signature[] =
+ "(I)Z";
+ static const bool isStatic = false;
+ static const mozilla::jni::ExceptionMode exceptionMode =
+ mozilla::jni::ExceptionMode::ABORT;
+ static const mozilla::jni::CallingThread callingThread =
+ mozilla::jni::CallingThread::ANY;
+ static const mozilla::jni::DispatchTarget dispatchTarget =
+ mozilla::jni::DispatchTarget::CURRENT;
+ };
+
+ auto SetRates(int32_t) const -> bool;
+
static const mozilla::jni::CallingThread callingThread =
mozilla::jni::CallingThread::ANY;
};
class CodecProxy::NativeCallbacks : public mozilla::jni::ObjectBase<NativeCallbacks>
{
public: