我的视频课程(基础):《(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

Android 用Mediacodec硬解码视频包AVpacket_第1张图片 Android 用Mediacodec硬解码视频包AVpacket_第2张图片

 

一、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. Android开发视频教程-深入浅出系列Lesson20-AFF111029学习Androi
  2. android的多媒体数据库
  3. android 启动过程及init.rc
  4. Android 根文件系统启动过程
  5. android主要有5中数据存储方式
  6. Android视频开发浅析
  7. Android数据存储--sqlite
  8. Android FrameWork――Touch事件派发过程详解
  9. android 横竖屏切换属性和播放视频全屏切换

随机推荐

  1. Android网络请求心路历程
  2. Android(安卓)设置投影效果
  3. Android知识体系结构概览
  4. 用cmd 命令更改Android(安卓)的默认虚拟
  5. Android横竖屏切换总结
  6. 微信小程序性能分析Trace工具
  7. Android(安卓)- ReactNative Debug 技巧
  8. 访问Android内部RIL接口(一)
  9. 开源项目之Android(安卓)undergarment
  10. android中使用adb shell命令kill掉应用程