我的视频课程(基础):《(NDK)FFmpeg打造Android万能音频播放器》

我的视频课程(进阶):《(NDK)FFmpeg打造Android视频播放器》

我的视频课程(编码直播推流):《Android视频编码和直播推流》

我的视频课程(C++ OpenGL):《Android C++ OpenGL教程》

 

 

FFmpeg是一个很不错的开源的音视频编解码库,其编解码器几乎涵盖所有格式的音视频。但是它是利用CPU来编解码的,在PC等设备上面解码能力还能满足需求,但是在移动设备上面解码720p及其以上的视频时就显得很尴尬了,解码速度不够导致解码视频帧的速度太慢,造成播放卡顿并且耗电也快。如果能用移动设备上的GPU来解码视频帧的话,那效率将会提高很多倍的,这就需要用到硬解码器MediaCodec了。

 

        FFmpeg解码出AVpacket的速度是完全够的,因此我们就会想如果我们能用MediaCodec来解码AVpacket包里面的视频原始压缩数据的话,那就能播放高清视频了并且还不会太耗电。幸好,经过测试,这种方式是完全可行的。

        那么开始我们的MediaCodec解码AVpacket之旅吧。还是先看效果:都为720p的视频(源码下载wlplayer

 

 

一、Mediacodec解码过程

1.1、首先的配置MediaFormat,来告诉MediaCodec解码的视频时怎样的,有哪些信息,如下代码:

 

public void mediacodecInit(int mimetype, int width, int height, byte[] csd0, byte[] csd1)    {        if(surface != null)        {            try {                wlGlSurfaceView.setCodecType(1);                String mtype = getMimeType(mimetype);                mediaFormat = MediaFormat.createVideoFormat(mtype, width, height);                mediaFormat.setInteger(MediaFormat.KEY_WIDTH, width);                mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, height);                mediaFormat.setLong(MediaFormat.KEY_MAX_INPUT_SIZE, width * height);                mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(csd0));                mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(csd1));                Log.d("ywl5320", mediaFormat.toString());                mediaCodec = MediaCodec.createDecoderByType(mtype);                if(surface != null)                {                    mediaCodec.configure(mediaFormat, surface, null, 0);                    mediaCodec.start();                }            } catch (Exception e) {                e.printStackTrace();            }        }        else        {            if(wlOnErrorListener != null)            {                wlOnErrorListener.onError(WlStatus.WL_STATUS_SURFACE_NULL, "surface is null");            }        }    }

参数mimeType:是告诉MediaCodec要解码的视频的编码格式,如:video/avc、video/hevc等。

 

width和height:表示视频的宽和长。

csd0和csd1:都对应于AVCodecContext里面的extradata字段。

这样就配置好了MediaFormat。

 

1.2、MediaCodec解码AVpacket:

        MediaCodec解码视频的过程为:其里面有2个Buffer队列,一个是InputBuffer队列,负责把视频压缩数据送给MediaCodec解码器解码,然后清空数据并以此循环指定结束;另一个是OutputBuffer队列,负责把MediaCodec解码后的数据给surface渲染,然后清空数据并以此循环指定结束;说白了就是一个负责往MediaCodec喂数据,一个负责把MediaCodec(排出的数据)送给surface渲染,循环这个过程,就能播放视频了。

        了解了MediaCodec的解码过程,我们就知道从何入手了,就在喂数据(InputBuffer)那里开刀,获取MediaCodec的InputBuffer,然后把AVpacket里面的视频压缩数据添加到里面,并用queueInputBuffer方法送给MediaCodec,这样MediaCodec就有了解码的原始数据,那么代码怎么写呢:

 

public void mediacodecDecode(byte[] bytes, int size, int pts)    {        if(bytes != null && mediaCodec != null && info != null)        {            try            {                int inputBufferIndex = mediaCodec.dequeueInputBuffer(10000);                if(inputBufferIndex >= 0)                {                    ByteBuffer byteBuffer = mediaCodec.getInputBuffers()[inputBufferIndex];                    byteBuffer.clear();                    byteBuffer.put(bytes);                    mediaCodec.queueInputBuffer(inputBufferIndex, 0, size, pts, 0);                }                int index = mediaCodec.dequeueOutputBuffer(info, 10000);                if (index >= 0) {                    //ByteBuffer buffer = mediaCodec.getOutputBuffers()[index];                    //buffer.position(info.offset);                    //buffer.limit(info.offset + info.size);                    mediaCodec.releaseOutputBuffer(index, true);                }            }catch (Exception e)            {                e.printStackTrace();            }        }    }

上面代码参数byte[]就是从C++传过来的AVpacket里面的视频原始压缩数据,然后获取InputBuffer并把byte数据添加到里面,最好送给MediaCodec。

 

解码过程没什么变化,和官方过程一样。

二、C++提供AVpacket数据:

2.1、封装调用Java的方法:

 

void WlJavaCall::onDecMediacodec(int type, int size, uint8_t *packet_data, int pts) {    if(type == WL_THREAD_CHILD)    {        JNIEnv *jniEnv;        if(javaVM->AttachCurrentThread(&jniEnv, 0) != JNI_OK)        {//            LOGE("%s: AttachCurrentThread() failed", __FUNCTION__);            return;        }        jbyteArray data = jniEnv->NewByteArray(size);        jniEnv->SetByteArrayRegion(data, 0, size, (jbyte*)packet_data);        jniEnv->CallVoidMethod(jobj, jmid_dec_mediacodec, data, size, pts);        jniEnv->DeleteLocalRef(data);        javaVM->DetachCurrentThread();    }    else    {        jbyteArray data = jniEnv->NewByteArray(size);        jniEnv->SetByteArrayRegion(data, 0, size, (jbyte*)data);        jniEnv->CallVoidMethod(jobj, jmid_dec_mediacodec, data, size, pts);        jniEnv->DeleteLocalRef(data);    }}

 

这里分了主线程和子线程,不过解码是在子线程的,所以不会用到主线程的。

2.2、添加数据头:

        因为AVpacket里面的压缩数据是很纯粹的,这种数据MediaCodec是不能解码或者解码出来也不能播放的,因此需要将AVpacket添加相应的数据头,这就要用到FFmpeg的av_bitstream_filter_filter方法,如:

 

mimType =  av_bitstream_filter_init("h264_mp4toannexb");if(mimType != NULL && !isavi)                {                    uint8_t *data;                    av_bitstream_filter_filter(mimType, pFormatCtx->streams[wlVideo->streamIndex]->codec, NULL, &data, &packet->size, packet->data, packet->size, 0);                    uint8_t *tdata = NULL;                    tdata = packet->data;                    packet->data = data;                    if(tdata != NULL)                    {                        av_free(tdata);                    }                }

注:这里会导致内存泄漏,经过av_bitstream_filter_filter处理的AVpacket的data的地址和原来的是不一样的,不释放原来的地址就会造成内存泄漏。

 

2.3、传递AVpacket数据到MediaCodec播放:

 

wljavaCall->onDecMediacodec(WL_THREAD_CHILD, packet->size, packet->data, clock);

直接把AVpacket的size和data传给MediaCodec就可以了。

 

2.4、释放AVpacket

由于我们在解复用时对AVpacket的data进行了操作,如果直接av_packet_free的话,会报释放地址错误,所以这里就单独释放AVpacket里面的指针就行了:

 

            av_free(packet->data);            av_free(packet->buf);            av_free(packet->side_data);            packet = NULL;

 

 

 

 

 

完整实例可参考:wlplayer 

 

OK,就这样了:多捣鼓总会成功的!

 

 

 


 

 

更多相关文章

  1. mybatisplus的坑 insert标签insert into select无参数问题的解决
  2. python起点网月票榜字体反爬案例
  3. Android数据可视化
  4. Android(安卓)webview与js 交换JSON对象数据
  5. Android(安卓)Spinner列表选择框的应用
  6. Android开发视频教程-深入浅出系列Lesson20-AFF111029学习Androi
  7. android的多媒体数据库
  8. android 中管理短信
  9. Android数据绑定Data Binding初体验

随机推荐

  1. Android setTheme设置透明主题无效
  2. android小知识
  3. Android MPAndroidChart超漂亮图表框架
  4. Android(安卓)Studio 编译慢解决方法
  5. 总结的Android中surfaceView清屏的方式。
  6. android百度地图(二)之定位
  7. android.intent.action.MEDIA_MOUNTED广
  8. Android 内存泄漏检测之Profiler
  9. Android中的数据库——SQLite
  10. Android之开发BLE 详细步骤