Skip to content

Commit e192311

Browse files
committed
Improve android media player, also fix #2101
a. handle cbcr offset properly b. handle output dim properly
1 parent 5851603 commit e192311

File tree

3 files changed

+62
-12
lines changed

3 files changed

+62
-12
lines changed

core/media/AndroidMediaEngine.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolMediaEngine_nativeHandleVideoSamp
4444
int outputY,
4545
int videoX,
4646
int videoY,
47+
int cbcrOffset,
4748
int rotation,
4849
int videoPF)
4950
{
@@ -53,7 +54,7 @@ JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolMediaEngine_nativeHandleVideoSamp
5354

5455
auto sampleData = static_cast<uint8_t*>(env->GetDirectBufferAddress(sampleBuffer));
5556

56-
mediaEngine->handleVideoSample(sampleData, sampleLen, outputX, outputY, videoX, videoY, rotation, videoPF);
57+
mediaEngine->handleVideoSample(sampleData, sampleLen, outputX, outputY, videoX, videoY, cbcrOffset, rotation, videoPF);
5758
}
5859

5960
JNIEXPORT void JNICALL Java_org_axmol_lib_AxmolMediaEngine_nativeSetDuration(JNIEnv* env,
@@ -162,7 +163,7 @@ bool AndroidMediaEngine::transferVideoFrame()
162163

163164
auto& buffer = _frameBuffer2;
164165

165-
ax::MEVideoFrame frame{buffer.data(), buffer.data() + _outputDim.x * _outputDim.y, buffer.size(),
166+
ax::MEVideoFrame frame{buffer.data(), buffer.data() + _cbcrOffset, buffer.size(),
166167
ax::MEVideoPixelDesc{static_cast<ax::MEVideoPixelFormat>(_videoPF), _outputDim}, _videoDim};
167168
frame._vpd._rotation = _videoRotation;
168169
assert(static_cast<int>(frame._dataLen) >= frame._vpd._dim.x * frame._vpd._dim.y * 3 / 2);
@@ -180,13 +181,15 @@ void AndroidMediaEngine::handleVideoSample(const uint8_t* buf,
180181
int outputY,
181182
int videoX,
182183
int videoY,
184+
int cbcrOffset,
183185
int rotation,
184186
int videoPF)
185187
{
186188
std::unique_lock<std::mutex> lck(_frameBuffer1Mtx);
187189
_frameBuffer1.assign(buf, buf + len);
188190
_outputDim.set(outputX, outputY);
189191
_videoDim.set(videoX, videoY);
192+
_cbcrOffset = cbcrOffset;
190193
_videoRotation = rotation;
191194
_videoPF = videoPF;
192195
}

core/media/AndroidMediaEngine.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class AndroidMediaEngine : public MediaEngine
6060
MEMediaState getState() const override;
6161
bool transferVideoFrame() override;
6262

63-
void handleVideoSample(const uint8_t* buf, size_t len, int outputX, int outputY, int videoX, int videoY, int rotation, int videoPF);
63+
void handleVideoSample(const uint8_t* buf, size_t len, int outputX, int outputY, int videoX, int videoY, int cbcrOffset, int rotation, int videoPF);
6464
void updateCurrentTime(double currentTime) { _currentTime = currentTime; }
6565
void updateDuration(double duration) { _duration = duration; }
6666

@@ -71,6 +71,7 @@ class AndroidMediaEngine : public MediaEngine
7171

7272
MEIntPoint _outputDim;
7373
MEIntPoint _videoDim;
74+
int _cbcrOffset{0};
7475
int _videoRotation{0};
7576
int _videoPF{-1};
7677

core/platform/android/java/src/org/axmol/lib/AxmolMediaEngine.java

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ of this software and associated documentation files (the "Software"), to deal
2929
import android.media.MediaCodecInfo;
3030
import android.media.MediaFormat;
3131
import android.net.Uri;
32+
import android.os.Build;
3233
import android.os.Handler;
3334
import android.util.Log;
3435

3536
import androidx.annotation.Nullable;
37+
import androidx.annotation.RequiresApi;
3638
import androidx.media3.common.Format;
3739
import androidx.media3.common.MediaItem;
3840
import androidx.media3.common.PlaybackException;
@@ -105,12 +107,14 @@ public class AxmolMediaEngine extends DefaultRenderersFactory implements Player.
105107
private AtomicInteger mState = new AtomicInteger(STATE_CLOSED);
106108
Point mOutputDim = new Point(); // The output dim match with buffer
107109
Point mVideoDim = new Point(); // The video dim (validate image dim)
110+
String mSampleMimeType = null;
108111
private int mVideoPF = -1;
109112
private int mVideoRotation = 0;
113+
private int mCbcrOffset = 0;
110114

111115
/** ------ native methods ------- */
112116
public static native void nativeHandleEvent(long nativeObj, int arg1);
113-
public static native void nativeHandleVideoSample(long nativeObj, ByteBuffer sampleData, int sampleLen, int outputX, int outputY, int videoX, int videoY, int rotation, int videoPF);
117+
public static native void nativeHandleVideoSample(long nativeObj, ByteBuffer sampleData, int sampleLen, int outputX, int outputY, int videoX, int videoY, int cbcrOffset, int rotation, int videoPF);
114118
public static native void nativeSetDuration(long nativeObj, double duration);
115119
public static native void nativeSetCurrentTime(long nativeObj, double currentTime);
116120

@@ -304,16 +308,18 @@ public void onVideoFrameAboutToBeRendered(
304308
Format format,
305309
@Nullable MediaFormat mediaFormat) {
306310
if (mOutputFormat != mediaFormat) {
311+
mSampleMimeType = format.sampleMimeType; // video/hevc, video/avc
307312
mOutputFormat = mediaFormat;
308-
updateVideoMeta();
313+
handleVideoMetaChanged();
309314
}
310315
}
311316

312-
/** update video informations */
313-
private void updateVideoMeta() {
317+
/** handle video informations changed */
318+
private void handleVideoMetaChanged() {
314319
MediaFormat format = mOutputFormat;
315320
if(format != null) {
316-
// String mimeType = format.getString(MediaFormat.KEY_MIME); // "video/raw"
321+
// String mimeType = format.getString(MediaFormat.KEY_MIME); // =="video/raw"
322+
317323
// Note: some android 11 and older devices not response desired color format(NV12), instead will be YUV420P aka I420
318324
// refer: https://github.com/axmolengine/axmol/issues/2049
319325
Integer colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
@@ -329,30 +335,70 @@ private void updateVideoMeta() {
329335
Log.w(TAG, String.format("Unsupported color format: %d, video render may incorrect!", colorFormat));
330336
}
331337

338+
// output dim
332339
mOutputDim.x = format.getInteger(MediaFormat.KEY_WIDTH);
340+
mOutputDim.y = format.getInteger(MediaFormat.KEY_HEIGHT);
341+
342+
int stride = 0, sliceHeight = 0;
343+
if (format.containsKey(MediaFormat.KEY_STRIDE)) {
344+
stride = format.getInteger(MediaFormat.KEY_STRIDE);
345+
}
346+
if (format.containsKey(MediaFormat.KEY_SLICE_HEIGHT)) {
347+
sliceHeight = format.getInteger(MediaFormat.KEY_SLICE_HEIGHT);
348+
}
349+
Log.d(TAG, String.format("Frame stride and slice height: %dx%d", stride, sliceHeight);
350+
stride = Math.max(mOutputDim.x, stride);
351+
sliceHeight = Math.max(mOutputDim.y, sliceHeight);
352+
353+
/* Notes
354+
* 1. About desired frame size bytes
355+
* a. stride > mOutputDim.x: means all frame bytes should pass to GPU(shader), and
356+
* desired frame size bytes is: stride * sliceHeight * 3 / 2
357+
* b. stride == mOutputDim.x: means we need discard Y plane aligned extra data, and
358+
* desired frame size bytes is: stride * sliceHeight + (mOutputDim.x / 2) * (mOutputDim.y / 2) * 2
359+
* 2. About video frame size alignment
360+
* a. many devices may align 2, the sliceHeight == mOutputDim.y and stride == mOutputDim.x
361+
* b. H264: align 16 for both width and height
362+
* HEVC/H265: align 32 for both width and height
363+
* 3. The cbcrOffset should be always stride * sliceHeight
364+
* refer: https://github.com/axmolengine/axmol/issues/2101
365+
*/
366+
mCbcrOffset = stride * sliceHeight;
367+
int frameSizeBytes = 0;
368+
if (stride > mOutputDim.x) {
369+
mOutputDim.x = stride;
370+
mOutputDim.y = sliceHeight;
371+
frameSizeBytes = mCbcrOffset * 3 / 2;
372+
} else frameSizeBytes = mCbcrOffset + mOutputDim.x / 2 * mOutputDim.y;
373+
374+
// video dim
333375
if (format.containsKey(MediaFormat.KEY_CROP_LEFT)
334376
&& format.containsKey(MediaFormat.KEY_CROP_RIGHT)) {
335377
mVideoDim.x = format.getInteger(MediaFormat.KEY_CROP_RIGHT) + 1
336378
- format.getInteger(MediaFormat.KEY_CROP_LEFT);
337379
} else
338380
mVideoDim.x = mOutputDim.x;
339381

340-
mOutputDim.y = format.getInteger(MediaFormat.KEY_HEIGHT);
341382
if (format.containsKey(MediaFormat.KEY_CROP_TOP)
342383
&& format.containsKey(MediaFormat.KEY_CROP_BOTTOM)) {
343384
mVideoDim.y = format.getInteger(MediaFormat.KEY_CROP_BOTTOM) + 1
344385
- format.getInteger(MediaFormat.KEY_CROP_TOP);
345386
} else
346387
mVideoDim.y = mOutputDim.y;
347388

389+
// video rotation
348390
if (format.containsKey(MediaFormat.KEY_ROTATION)) {
349391
mVideoRotation = format.getInteger(MediaFormat.KEY_ROTATION);
350392
}
393+
394+
Log.d(TAG, String.format("Input format:%s, outputDim:%dx%d, videoDim:%dx%d, cbcrOffset:%d, frameSizeBytes:%d", mSampleMimeType,
395+
mOutputDim.x, mOutputDim.y,
396+
mVideoDim.x, mVideoDim.y,
397+
mCbcrOffset, frameSizeBytes));
351398
}
352399
}
353400

354401
/** handler or listener methods */
355-
356402
@Override
357403
public void processVideoFrame(MediaCodecAdapter codec, int index, long presentationTimeUs) {
358404
if (mState.get() != STATE_PLAYING) {
@@ -362,7 +408,7 @@ public void processVideoFrame(MediaCodecAdapter codec, int index, long presentat
362408
}
363409

364410
ByteBuffer tmpBuffer = codec.getOutputBuffer(index);
365-
nativeHandleVideoSample(mNativeObj, tmpBuffer, tmpBuffer.remaining(), mOutputDim.x, mOutputDim.y, mVideoDim.x, mVideoDim.y, mVideoRotation, mVideoPF);
411+
nativeHandleVideoSample(mNativeObj, tmpBuffer, tmpBuffer.remaining(), mOutputDim.x, mOutputDim.y, mVideoDim.x, mVideoDim.y, mCbcrOffset, mVideoRotation, mVideoPF);
366412

367413
AxmolEngine.getActivity().runOnUiThread(() -> {
368414
if (mPlayer != null) {
@@ -437,7 +483,7 @@ public void onVideoSizeChanged(VideoSize videoSize) {
437483
Log.d(TAG, String.format("[Individual]onVideoSizeChanged: (%d,%d)", videoSize.width, videoSize.height));
438484

439485
if(mPlayer != null)
440-
updateVideoMeta();
486+
handleVideoMetaChanged();
441487
}
442488

443489
@Override

0 commit comments

Comments
 (0)