videoExtractor在打开有角度视频文件mediaFormat.getInteger(MediaFormat.KEY_ROTATION);获取角度
MediaFormat mediaFormat = videoExtractor.getTrackFormat(j);
String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith(KEY_VIDEO)) {//匹配视频对应的轨道
videoExtractor.selectTrack(j);//选择视频对应的轨道
long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
int degrees = 0;
try {
if (mediaFormat.containsKey(MediaFormat.KEY_ROTATION))
degrees = mediaFormat.getInteger(MediaFormat.KEY_ROTATION);//有些视频没这个参数会空指针
} catch (Exception e) {
e.printStackTrace();
}
}
在MediaCodec在创建和configure时候传入角度,解码 mediaCodec.releaseOutputBuffer(outputBufferIndex, true);在渲染时候你发现你的视频正的,并没有旋转角度。
mediaCodec = MediaCodec.createDecoderByType("video/avc");
mediaCodec.configure(mediaFormat surface, null, 0);
上面根据android的api和framework你可以发现,底层在mediaCodec.releaseOutputBuffer(outputBufferIndex, true);在渲染时候进行旋转,解码并没有帮你旋转角度,验证只有当MediaCodec直接解码到Surface时,旋转角度才有效。
1、MediaCodec::configure配置解码器,MediaFormat作为参数。
2、MediaCodec发送kWhatConfigure消息,在自有线程配置解码器。
3、调用ACodec->initiateConfigureComponent(MediaFormat为参数)配置ACodec。
4、ACodec发送kWhatConfigureComponent消息在自有线程配置ACodec。
5、接着是ACodec::LoadedState::onConfigureComponent方法。
6、然后是ACodec.configureCodec方法,负责通过MediaFormat配置ACodec,其中从format中取出了"rotation-degrees",保存在ACodec.mRotationDegrees变量中。
7、最后,ACodec.setupNativeWindowSizeFormatAndUsage中调用全局函数setNativeWindowSizeFormatAndUsage为Surface(ANativeWindow)设置transform flag。核心代码在setNativeWindowSizeFormatAndUsage函数中:
// 根据旋转角度获得transform flag
int transform = 0;
if ((rotation % 90) == 0) {
switch ((rotation / 90) & 3) {
case 1: transform = HAL_TRANSFORM_ROT_90; break;
case 2: transform = HAL_TRANSFORM_ROT_180; break;
case 3: transform = HAL_TRANSFORM_ROT_270; break;
default: transform = 0; break;
}
}
// 为Surface(ANativeWindow)设置transform
err = native_window_set_buffers_transform(nativeWindow, transform);
从上面借鉴MediaCodec,正常是可以的实现的,通过ndk和android的api是可以实现的,ndk提供ANativeWindow_setBuffersTransform方法。
实现条件:
1、调用实现在GLSurfaceView配置出OpenGL环境获取surface在jni转换nativewindow
2、gradle 的android版本8.0也就是只兼容26以上 minSdkVersion 26
3、CMakeLists要关联nativewindow库
实现代码:
关键头文件#include
void WlVideo::postDataFromNative(const AVFrame *avFrame,int angle) {
int retval;
char overlay_format[5] = {'Y', 'V', '1', '2', 0};
int curr_w = ANativeWindow_getWidth(native_window);
int curr_h = ANativeWindow_getHeight(native_window);
int curr_format = ANativeWindow_getFormat(native_window);
int buff_w = avFrame->width;
int buff_h = avFrame->height;
if (curr_format != HAL_PIXEL_FORMAT_YV12) {
LOGE("ANativeWindow_setBuffersGeometry: w=%d, h=%d, f=%.4s(0x%x) => w=%d, h=%d, f=%.4s",
curr_w, curr_h, (char *) &curr_format, curr_format,
buff_w, buff_h, (char *) overlay_format);
retval = ANativeWindow_setBuffersGeometry(native_window, buff_w, buff_h,
HAL_PIXEL_FORMAT_YV12);
if (retval < 0) {
LOGE("ANativeWindow_setBuffersGeometry: failed %d", retval);
return;
}
}
ANativeWindow_Buffer out_buffer;
retval = ANativeWindow_lock(native_window, &out_buffer, NULL);
if (retval < 0) {
LOGE("ANativeWindow_lock: failed %d", retval);
return;
}
LOGE("postDataFromNative1 native window buffer (%p)(out_buffer.width:%d, out_buffer.height:%d, out_buffer.format:'%.4s'0x%x), expecting (buff_w:%d, buff_h:%d, overlay_format:'%.4s')",
native_window,
out_buffer.width, out_buffer.height, (char *) &out_buffer.format, out_buffer.format,
buff_w, buff_h, (char *) overlay_format);
if (out_buffer.width != buff_w || out_buffer.height != buff_h) {
LOGE("unexpected native window buffer (%p)(w:%d, h:%d, fmt:'%.4s'0x%x), expecting (w:%d, h:%d, fmt:'%.4s')",
native_window,
out_buffer.width, out_buffer.height, (char *) &out_buffer.format, out_buffer.format,
buff_w, buff_h, (char *) overlay_format);
// TODO: 8 set all black
ANativeWindow_unlockAndPost(native_window);
ANativeWindow_setBuffersGeometry(native_window, buff_w, buff_h, HAL_PIXEL_FORMAT_YV12);
return;
}
int render_ret = android_render_yv12_on_yv12(&out_buffer, avFrame);
if (render_ret < 0) {
// TODO: 8 set all black
// return after unlock image;
}
/**
* minSdkVersion=26 gradle的sdk版本要大26
* CMakeLists.txt需要 target_link_libraries 加入ndk的nativewindow不然会报错undefined reference to 'ANativeWindow_setBuffersTransform'
* ANativeWindow_setBuffersTransform作用就参考mediacodec,解码渲染时候对有角度的进行旋转
*/
switch (angle) {
case 90:
retval = ANativeWindow_setBuffersTransform(native_window, ANATIVEWINDOW_TRANSFORM_ROTATE_90);
break;
case 180:
retval = ANativeWindow_setBuffersTransform(native_window, ANATIVEWINDOW_TRANSFORM_ROTATE_180);
break;
case 270:
retval = ANativeWindow_setBuffersTransform(native_window, ANATIVEWINDOW_TRANSFORM_ROTATE_270);
break;
default:
break;
}
if (retval < 0) {
LOGE("ANativeWindow_setBuffersTransform: failed %d", retval);
}
LOGE("ANativeWindow_lock: ============= %d", render_ret);
retval = ANativeWindow_unlockAndPost(native_window);
if (retval < 0) {
LOGE("ANativeWindow_unlockAndPost: failed %d", retval);
return;
}
return;
}
上面方法对应的 int WlVideo::android_render_yv12_on_yv12(ANativeWindow_Buffer *out_buffer, const AVFrame *avFrame) 是yuv420p对ANativeWindow的数据填充,当数据填充完后SurfaceTexture.OnFrameAvailableListener对应的监听方法就会被调用可以onFrameAvailable刷新OpenGL渲染
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
Log.e("========","========onRender====");
surfaceView.requestRender();
}