MediaPlayer2: use protobuf instead of parcel in invoke()

Also fix MEDIA_INFO handling code to send the event to the client
after handling internal update. (This fixes several cts test cases.)

Test: pass MediaPlayer2Test
Bug: 63934228
Change-Id: I5d4884353057a195b1f587694bfbf66cdf1fd23c
This commit is contained in:
Dongwon Kang
2018-07-10 12:42:58 -07:00
parent bda1e07cba
commit dfea0aab67
8 changed files with 199 additions and 137 deletions

View File

@@ -679,6 +679,7 @@ java_library {
static_libs: [
"framework-protos",
"mediaplayer2-protos",
"android.hidl.base-V1.0-java",
"android.hardware.cas-V1.0-java",
"android.hardware.contexthub-V1.0-java",

View File

@@ -772,22 +772,6 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener
return null;
}
/**
* Invoke a generic method on the native player using opaque
* parcels for the request and reply. Both payloads' format is a
* convention between the java caller and the native player.
* Must be called after setDataSource to make sure a native player
* exists. On failure, a RuntimeException is thrown.
*
* @param request Parcel with the data for the extension. The
* caller must use {@link #newRequest()} to get one.
*
* @param reply Output parcel with the data returned by the
* native player.
* {@hide}
*/
public void invoke(Parcel request, Parcel reply) { }
/**
* Insert a task in the command queue to help the client to identify whether a batch
* of commands has been finished. When this command is processed, a notification

View File

@@ -25,6 +25,9 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer2Proto;
import android.media.MediaPlayer2Proto.PlayerMessage;
import android.media.MediaPlayer2Proto.Value;
import android.media.SubtitleController.Anchor;
import android.media.SubtitleTrack.RenderingWidget;
import android.net.Uri;
@@ -49,6 +52,7 @@ import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.VideoView;
import com.android.framework.protobuf.InvalidProtocolBufferException;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -72,6 +76,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -555,28 +560,30 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
}
/**
* Invoke a generic method on the native player using opaque
* parcels for the request and reply. Both payloads' format is a
* Invoke a generic method on the native player using opaque protocol
* buffer message for the request and reply. Both payloads' format is a
* convention between the java caller and the native player.
* Must be called after setDataSource or setPlaylist to make sure a native player
* exists. On failure, a RuntimeException is thrown.
*
* @param request Parcel with the data for the extension. The
* @param request PlayerMessage for the extension. The
* caller must use {@link #newRequest()} to get one.
*
* @param reply Output parcel with the data returned by the
* @return PlayerMessage with the data returned by the
* native player.
* {@hide}
*/
@Override
public void invoke(Parcel request, Parcel reply) {
int retcode = native_invoke(request, reply);
reply.setDataPosition(0);
if (retcode != 0) {
throw new RuntimeException("failure code: " + retcode);
private PlayerMessage invoke(PlayerMessage msg) {
byte[] ret = _invoke(msg.toByteArray());
if (ret == null) {
return null;
}
try {
return PlayerMessage.parseFrom(ret);
} catch (InvalidProtocolBufferException e) {
return null;
}
}
private native byte[] _invoke(byte[] request);
@Override
public void notifyWhenCommandLabelReached(Object label) {
addTask(new Task(CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED, false) {
@@ -683,16 +690,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
final String msg = "Scaling mode " + mode + " is not supported";
throw new IllegalArgumentException(msg);
}
Parcel request = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
request.writeInt(INVOKE_ID_SET_VIDEO_SCALE_MODE);
request.writeInt(mode);
invoke(request, reply);
} finally {
request.recycle();
reply.recycle();
}
PlayerMessage request = PlayerMessage.newBuilder()
.addValues(Value.newBuilder()
.setInt32Value(INVOKE_ID_SET_VIDEO_SCALE_MODE))
.addValues(Value.newBuilder().setInt32Value(mode))
.build();
invoke(request);
}
});
}
@@ -1798,14 +1801,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
private native void _setAuxEffectSendLevel(float level);
/*
* @param request Parcel destinated to the media player.
* @param reply[out] Parcel that will contain the reply.
* @return The status code.
*/
private native final int native_invoke(Parcel request, Parcel reply);
/*
* @param update_only If true fetch only the set of metadata that have
* changed since the last invocation of getMetadata.
@@ -1886,18 +1881,18 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
final int mTrackType;
final MediaFormat mFormat;
TrackInfoImpl(Parcel in) {
mTrackType = in.readInt();
// TODO: parcel in the full MediaFormat; currently we are using createSubtitleFormat
TrackInfoImpl(Iterator<Value> in) {
mTrackType = in.next().getInt32Value();
// TODO: build the full MediaFormat; currently we are using createSubtitleFormat
// even for audio/video tracks, meaning we only set the mime and language.
String mime = in.readString();
String language = in.readString();
String mime = in.next().getStringValue();
String language = in.next().getStringValue();
mFormat = MediaFormat.createSubtitleFormat(mime, language);
if (mTrackType == MEDIA_TRACK_TYPE_SUBTITLE) {
mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.readInt());
mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.readInt());
mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.readInt());
mFormat.setInteger(MediaFormat.KEY_IS_AUTOSELECT, in.next().getInt32Value());
mFormat.setInteger(MediaFormat.KEY_IS_DEFAULT, in.next().getInt32Value());
mFormat.setInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE, in.next().getInt32Value());
}
}
@@ -1952,23 +1947,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
out.append("}");
return out.toString();
}
/**
* Used to read a TrackInfoImpl from a Parcel.
*/
/* package private */ static final Parcelable.Creator<TrackInfoImpl> CREATOR
= new Parcelable.Creator<TrackInfoImpl>() {
@Override
public TrackInfoImpl createFromParcel(Parcel in) {
return new TrackInfoImpl(in);
}
@Override
public TrackInfoImpl[] newArray(int size) {
return new TrackInfoImpl[size];
}
};
};
// We would like domain specific classes with more informative names than the `first` and `second`
@@ -2010,17 +1988,23 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
}
private TrackInfoImpl[] getInbandTrackInfoImpl() throws IllegalStateException {
Parcel request = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
request.writeInt(INVOKE_ID_GET_TRACK_INFO);
invoke(request, reply);
TrackInfoImpl trackInfo[] = reply.createTypedArray(TrackInfoImpl.CREATOR);
return trackInfo;
} finally {
request.recycle();
reply.recycle();
PlayerMessage request = PlayerMessage.newBuilder()
.addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_TRACK_INFO))
.build();
PlayerMessage response = invoke(request);
if (response == null) {
return null;
}
Iterator<Value> in = response.getValuesList().iterator();
int size = in.next().getInt32Value();
if (size == 0) {
return null;
}
TrackInfoImpl trackInfo[] = new TrackInfoImpl[size];
for (int i = 0; i < size; ++i) {
trackInfo[i] = new TrackInfoImpl(in);
}
return trackInfo;
}
/*
@@ -2481,26 +2465,24 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
}
}
Parcel request = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
request.writeInt(INVOKE_ID_GET_SELECTED_TRACK);
request.writeInt(trackType);
invoke(request, reply);
int inbandTrackIndex = reply.readInt();
synchronized (mIndexTrackPairs) {
for (int i = 0; i < mIndexTrackPairs.size(); i++) {
Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
if (p.first != null && p.first == inbandTrackIndex) {
return i;
}
PlayerMessage request = PlayerMessage.newBuilder()
.addValues(Value.newBuilder().setInt32Value(INVOKE_ID_GET_SELECTED_TRACK))
.addValues(Value.newBuilder().setInt32Value(trackType))
.build();
PlayerMessage response = invoke(request);
if (response == null) {
return -1;
}
int inbandTrackIndex = response.getValues(0).getInt32Value();
synchronized (mIndexTrackPairs) {
for (int i = 0; i < mIndexTrackPairs.size(); i++) {
Pair<Integer, SubtitleTrack> p = mIndexTrackPairs.get(i);
if (p.first != null && p.first == inbandTrackIndex) {
return i;
}
}
return -1;
} finally {
request.recycle();
reply.recycle();
}
return -1;
}
/**
@@ -2617,16 +2599,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
private void selectOrDeselectInbandTrack(int index, boolean select)
throws IllegalStateException {
Parcel request = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
request.writeInt(select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK);
request.writeInt(index);
invoke(request, reply);
} finally {
request.recycle();
reply.recycle();
}
PlayerMessage request = PlayerMessage.newBuilder()
.addValues(Value.newBuilder().setInt32Value(
select? INVOKE_ID_SELECT_TRACK: INVOKE_ID_DESELECT_TRACK))
.addValues(Value.newBuilder().setInt32Value(index))
.build();
invoke(request);
}
// Have to declare protected for finalize() since it is protected
@@ -2935,20 +2913,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
case MEDIA_INFO:
{
synchronized (mEventCbLock) {
for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
cb.first.execute(() -> cb.second.onInfo(
mMediaPlayer, dsd, what, extra));
}
}
switch (msg.arg1) {
case MEDIA_INFO_DATA_SOURCE_START:
if (isCurrentSrcId) {
prepareNextDataSource();
}
break;
case MEDIA_INFO_VIDEO_TRACK_LAGGING:
Log.i(TAG, "Info (" + msg.arg1 + "," + msg.arg2 + ")");
break;
@@ -2980,6 +2945,20 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
}
break;
}
synchronized (mEventCbLock) {
for (Pair<Executor, EventCallback> cb : mEventCallbackRecords) {
cb.first.execute(() -> cb.second.onInfo(
mMediaPlayer, dsd, what, extra));
}
}
if (msg.arg1 == MEDIA_INFO_DATA_SOURCE_START) {
if (isCurrentSrcId) {
prepareNextDataSource();
}
}
// No real default action so far.
return;
}

View File

@@ -133,8 +133,10 @@ cc_library_shared {
"libmediaextractor",
"libmediametrics",
"libmediaplayer2",
"libmediaplayer2-protos",
"libmediautils",
"libnetd_client",
"libprotobuf-cpp-lite",
"libstagefright_esds",
"libstagefright_foundation",
"libstagefright_httplive",

View File

@@ -56,6 +56,10 @@
#include "android_util_Binder.h"
#include <binder/Parcel.h>
#include "mediaplayer2.pb.h"
using android::media::MediaPlayer2Proto::PlayerMessage;
// Modular DRM begin
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
@@ -196,6 +200,11 @@ void JNIMediaPlayer2Listener::notify(int64_t srcId, int msg, int ext1, int ext2,
if (obj && obj->dataSize() > 0) {
jobject jParcel = createJavaParcelObject(env);
if (jParcel != NULL) {
// TODO: replace the following Parcel usages with proto.
// 1. MEDIA_DRM_INFO : DrmInfo
// 2. MEDIA_TIMED_TEXT : TimedText
// 2. MEDIA_SUBTITLE_DATA : SubtitleData
// 4. MEDIA_META_DATA : TimedMetaData
Parcel* nativeParcel = parcelForJavaObject(env, jParcel);
nativeParcel->setData(obj->data(), obj->dataSize());
env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
@@ -911,6 +920,9 @@ android_media_MediaPlayer2_setParameter(JNIEnv *env, jobject thiz, jint key, job
return false;
}
// TODO: parcelForJavaObject() shouldn't be used since it's dependent on
// framework's Parcel implementation. This setParameter() is used
// only with AudioAttribute. Can this be used as jobject with JAudioTrack?
Parcel *request = parcelForJavaObject(env, java_request);
status_t err = mp->setParameter(key, *request);
if (err == OK) {
@@ -978,26 +990,35 @@ android_media_MediaPlayer2_setVolume(JNIEnv *env, jobject thiz, jfloat leftVolum
process_media_player_call( env, thiz, mp->setVolume((float) leftVolume, (float) rightVolume), NULL, NULL );
}
// Sends the request and reply parcels to the media player via the
// binder interface.
static jint
android_media_MediaPlayer2_invoke(JNIEnv *env, jobject thiz,
jobject java_request, jobject java_reply)
{
static jbyteArray
android_media_MediaPlayer2_invoke(JNIEnv *env, jobject thiz, jbyteArray requestData) {
sp<MediaPlayer2> media_player = getMediaPlayer(env, thiz);
if (media_player == NULL ) {
if (media_player == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", NULL);
return UNKNOWN_ERROR;
return NULL;
}
Parcel *request = parcelForJavaObject(env, java_request);
Parcel *reply = parcelForJavaObject(env, java_reply);
// Get the byte[] pointer and data length.
jbyte* pData = env->GetByteArrayElements(requestData, NULL);
jsize pDataLen = env->GetArrayLength(requestData);
request->setDataPosition(0);
// Deserialize from the byte stream.
PlayerMessage request;
PlayerMessage response;
request.ParseFromArray(pData, pDataLen);
// Don't use process_media_player_call which use the async loop to
// report errors, instead returns the status.
return (jint) media_player->invoke(*request, reply);
media_player->invoke(request, &response);
int size = response.ByteSize();
jbyte* temp = new jbyte[size];
response.SerializeToArray(temp, size);
// return the response as a byte array.
jbyteArray out = env->NewByteArray(size);
env->SetByteArrayRegion(out, 0, size, temp);
delete[] temp;
return out;
}
// Sends the new filter to the client.
@@ -1512,7 +1533,7 @@ static const JNINativeMethod gMethods[] = {
{"setLooping", "(Z)V", (void *)android_media_MediaPlayer2_setLooping},
{"isLooping", "()Z", (void *)android_media_MediaPlayer2_isLooping},
{"_setVolume", "(FF)V", (void *)android_media_MediaPlayer2_setVolume},
{"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer2_invoke},
{"_invoke", "([B)[B", (void *)android_media_MediaPlayer2_invoke},
{"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer2_setMetadataFilter},
{"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer2_getMetadata},
{"native_init", "()V", (void *)android_media_MediaPlayer2_native_init},

20
media/proto/Android.bp Normal file
View File

@@ -0,0 +1,20 @@
java_library_static {
name: "mediaplayer2-protos",
host_supported: true,
proto: {
type: "lite",
},
srcs: ["mediaplayer2.proto"],
no_framework_libs: true,
jarjar_rules: "jarjar-rules.txt",
}
cc_library_static {
name: "libmediaplayer2-protos",
host_supported: true,
proto: {
export_proto_headers: true,
type: "lite",
},
srcs: ["mediaplayer2.proto"],
}

View File

@@ -0,0 +1,2 @@
rule com.google.protobuf.** com.android.framework.protobuf.@1

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018 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.
*/
syntax = "proto2";
option optimize_for = LITE_RUNTIME;
// C++ namespace: android::media:MediaPlayer2Proto:
package android.media.MediaPlayer2Proto;
option java_package = "android.media";
option java_outer_classname = "MediaPlayer2Proto";
message Value {
// The kind of value.
oneof kind {
// Represents a boolean value.
bool bool_value = 1;
// Represents an int32 value.
int32 int32_value = 2;
// Represents an uint32 value.
uint32 uint32_value = 3;
// Represents an int64 value.
int64 int64_value = 4;
// Represents an uint64 value.
uint64 uint64_value = 5;
// Represents a float value.
double float_value = 6;
// Represents a double value.
double double_value = 7;
// Represents a string value.
string string_value = 8;
// Represents a bytes value.
bytes bytes_value = 9;
}
}
message PlayerMessage {
repeated Value values = 1;
}