surfaceholder

1、使用MediaCodec目的

MediaCodec是Android底层开放核心的一部分。它通常与MediaExtractor、MediaMuxer和AudioTrack结合使用,用于编码常见的音频和视频格式,如H264、H265、AAC和3gp。

MediaCodec通过处理输入数据来生成输出数据。

1.1 MediaCodec工作流程

MediaCodec的数据流分为输入和输出流,这两个数据流是异步处理的。在手动释放输出缓冲区之前,MediaCodec不会完成数据处理。

input流:客户端输入待解码或者待编码的数据output流:客户端输出的已解码或者已编码的数据1.2 MediaCodec API说明getInputBuffers:获取需要输入流队列,返回ByteBuffer数组queueInputBuffer:输入流入队dequeueInputBuffer: 从输入流队列中取数据进行编码操作getOutputBuffers:获取已经编解码之后的数据输出流队列,返回ByteBuffer数组dequeueOutputBuffer:从输出队列中取出已经编码操作之后的数据releaseOutputBuffer: 处理完成,释放output缓冲区1.3 基本流程MediaCodec的基本使用遵循上图所示,它的生命周期如下所示:Stoped:创建好MediaCodec,进行配置,或者出现错误Uninitialized: 当创建了一个MediaCodec对象,此时MediaCodec处于Uninitialized,在任何状态调用reset()方法使MediaCodec返回到Uninitialized状态Configured: 使用configure(…)方法对MediaCodec进行配置转为Configured状态Error: 出现错误Executing:可以在Executing状态的任何时候通过调用flush()方法返回到Flushed状态Flushed:调用start()方法后MediaCodec立即进入Flushed状态Running:调用dequeueInputBuffer后,MediaCodec就转入Running状态End-of-Stream:编解码结束后,MediaCodec将转入End-of-Stream子状态Released:当使用完MediaCodec后,必须调用release()方法释放其资源1.4 基本使用

//decoder val m video decoder = media codec . create decoder bytype(& # 34;视频/C & # 34;)//encoder val m video encoder = media codec . create encoder bytype(& # 34;视频/C & # 34;) 2.MediaCodec解码H264/H265使用MediaCodec解码H264/H265视频,必须要说一下MediaCodec的伪像。附官网数据流程图如下:

surfaceholder

输入:ByteBuffer输入方;

Output:ByteBuffer输出方;

使用者从MediaCodec请求一个空的输入buffer(ByteBuffer),填充满数据后将它传递给MediaCodec处理。MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码。

如何免费获取C++音视频学习资料:关注音视频开发,点击“链接”即可免费获取2023年C++音视频开发最新独家免费学习包!

2.1 H264码流解码示例代码如下(基本都做了注释)

包com . zqfdev . h 264 decode demo;导入Android . media . media codec;导入Android . media . media format;导入Android . util . log;导入Android . view . surface;导入Ja . io . bytearrayoutputstream;导入Ja . io . data inputstream;导入Ja . io . file;导入Ja . io . file inputstream;导入Ja . io . io exception;导入Ja . io . inputstream;导入Ja . nio . byte buffer;/* * * @ author zhangqingfa * @ createdate 2020/12/10 11:39 * @ description解码H264 play */public类H264解码play { private static final string tag = & # 34;zqf-dev & # 34;;//视频路径私有字符串video path//使用android MediaCodec解码私有MediaCodec mediaCodec私有表面表面;H264DeCodePlay(String videoPath,Surface Surface){ this . video path = video path;this.surface =表面;initMediaCodec();} private void initMediaCodec(){ try { log . e(TAG,& # 34;视频路径& # 34;+video path);//创建解码器H264的类型是AAC mediacodec = mediacodec。CreateDecoderBytype(& # 34;视频/C & # 34;);//创建配置media format media format = media format . createvideoformat(& # 34;视频/C & # 34;, 540, 960);//设置解码的期望帧率【视频格式帧率的key,以每秒帧数为单位】media format . Set integer(media format . key _ frame _ rate,15);//配置绑定mediaFormat和surface media codec . configure(media format,surface,null,0);} catch(io exception e){ e . printstacktrace();//创建一个Log.e(标签,& # 34;创建解码失败& # 34;);}}/* * *解码播放*/Void解码播放(){mediacodec。start();新线程(new MyRun())。start();}私有类my run实现runnable { @ override public void run(){ try {//1,IO流读取h264文件【批量加载视频过大】bytes = nullbytes = getBytes(video path);Log.e(标签,& # 34;字节大小& # 34;+bytes . length);//2.获取media codec[]byte buffer[]input buffers = media codec的所有队列缓冲区。getinputBuffers();//起始位置int startIndex = 0;//h264总字节数int totalSize = bytes.length//3.解析while (true) {//判断是否符合if(totalSize = = 0 | | startIndex > = totalSize){ break;}//查找索引int next frame start = findbyframe(bytes,startindex+1,总大小);if(nextFrameStart = =-1)break;媒体编解码器。BufferInfo info =新媒体编解码器。buffer info();//查询10000ms后,如果dSP芯片所有缓冲区都被占用,返回-1;如果存在,则在index = mediacodec中大于0 int。出队输入缓冲区(10000);if(in index & gt;= 0) {//根据返回的索引得到可用缓冲字节buffer = input buffers[in index];//clear空cache byte buffer . clear();//开始为缓冲区填充数据bytebuffer.put (bytes,startindex,next frame Start-startindex);//通知mediacodec查询这个缓冲区,MediaCodec。填充数据后对inIndex的输入缓冲区(InIndex,0,NextFrame Start-StartIndex,0,0)进行排队;//准备下一帧,开始是前一帧的结束。startIndex = nextFrameStart} else {//等待查询的缓冲区继续空;} //mediaCodec查询& # 34;MediaCodec的输出队列& # 34;获取索引int out index = mediacodec。dequeueouputbuffer (info,10000);Log.e(标签,& # 34;outIndex & # 34+outIndex);if(out index & gt;= 0) {try {//在休眠Thread.sleep模式下暂时减慢播放速度(33);} catch(interrupted exception e){ e . printstacktrace();}//如果绑定了表面,直接输入到表面进行渲染,释放mediacodec。releasesoutputbuffer (out index,true);} else { Log.e(标签,& # 34;没有解码成功& # 34;);} } } catch(io exception e){ e . printstacktrace();} } }//读取一帧数据private int findbyframe(byte[]bytes,int start,int total size){ for(int I = start;我& lttotal size-4;i++){//output . h264文件的分析可以通过分隔符读取真实数据if(bytes[I]= = 0x 00 & & bytes[I+1]= = 0x 00 & & bytes[I+2]= = 0x 00 & & bytes[I+3])。} } return-1;} private byte[]getBytes(String video path)抛出io exception { InputStream is = new data InputStream(new File InputStream(new File(video path)));int lenint size = 1024byte[]buf;ByteArrayOutputStream Bos = new ByteArrayOutputStream();buf =新字节[大小];while ((len = is.read(buf,0,size))!= -1) bos.write(buf,0,len);buf = Bos . tobytearray();返回buf}}2.2 H265示例代码如下:package com . zqfdev . h 264 dode demo;导入Android . media . media codec;导入Android . media . media format;导入Android . util . log;导入Android . view . surface;导入Ja . io . bytearrayoutputstream;导入Ja . io . data inputstream;导入Ja . io . file;导入Ja . io . file inputstream;导入Ja . io . io exception;导入Ja . io . inputstream;导入Ja . nio . byte buffer;/* * * @ author zhangqingfa * @ createdate 2020/12/10 11:39 * @ description解码H264 play */public类H265解码play { private static final string tag = & # 34;zqf-dev & # 34;;//视频路径私有字符串video path//使用android MediaCodec解码私有MediaCodec mediaCodec私有表面表面;H265DeCodePlay(String videoPath,Surface Surface){ this . video path = video path;this.surface =表面;initMediaCodec();} private void initMediaCodec(){ try { log . e(TAG,& # 34;视频路径& # 34;+video path);//创建解码器H264的类型是AAC mediacodec = mediacodec。CreateDecoderBytype(& # 34;视频/hevc & # 34;);//创建配置media format media format = media format . createvideoformat(& # 34;视频/hevc & # 34;, 368, 384);//设置解码的预期帧率【视频格式帧率的关键,以每秒帧数为单位】mediaformat。setinteger (mediaformat。关键帧速率,15);//配置绑定mediaFormat和surface media codec . configure(media format,surface,null,0);} catch(io exception e){ e . printstacktrace();//创建一个Log.e(标签,& # 34;创建解码失败& # 34;);}}/* * *解码播放*/Void解码播放(){mediacodec。start();新线程(new MyRun())。start();}私有类my run实现runnable { @ override public void run(){ try {//1,IO流读取h264文件【批量加载视频过大】bytes = nullbytes = getBytes(video path);Log.e(标签,& # 34;字节大小& # 34;+bytes . length);//2.获取media codec[]byte buffer[]input buffers = media codec的所有队列缓冲区。getinputBuffers();//起始位置int startIndex = 0;//h264总字节数int totalSize = bytes.length//3.解析while (true) {//判断是否符合if(totalSize = = 0 | | startIndex > = totalSize){ break;}//查找索引int next frame start = findbyframe(bytes,startindex+1,总大小);if(nextFrameStart = =-1)break;Log.e(标签,& # 34;nextFrameStart & # 34+next frame start);媒体编解码器。BufferInfo info =新媒体编解码器。buffer info();//查询10000毫秒后,如果dSP芯片的所有缓冲区都被占用,返回-1;如果存在,则在index = mediacodec中大于0 int。出队输入缓冲区(10000);if(in index & gt;= 0) {//根据返回的索引得到可用缓冲字节buffer = input buffers[in index];//clear 空byteBuffer缓存byte buffer . clear();//开始为缓冲区填充数据bytebuffer.put (bytes,startindex,next frame Start-startindex);//通知mediacodec查询这个缓冲区,MediaCodec。填充数据后对inIndex的输入缓冲区(InIndex,0,NextFrame Start-StartIndex,0,0)进行排队;//准备下一帧,开始是前一帧的结束。startIndex = nextFrameStart} else {//等待查询的缓冲区继续空;} //mediaCodec查询& # 34;MediaCodec的输出队列& # 34;获取索引int out index = mediacodec。dequeueouputbuffer (info,10000);Log.e(标签,& # 34;outIndex & # 34+outIndex);if(out index & gt;= 0) {try {//在休眠Thread.sleep(33)模式下暂时减慢播放速度;} catch(interrupted exception e){ e . printstacktrace();}//如果绑定了表面,直接输入到表面进行渲染,释放mediacodec。releasesoutputbuffer (out index,true);} else { Log.e(标签,& # 34;没有解码成功& # 34;);} } } catch(io exception e){ e . printstacktrace();} } }//读取一帧数据private int findbyframe(byte[]bytes,int start,int total size){ for(int I = start;我& lttotal size-4;i++){//output . h264文件的分析可以通过分隔符读取真实数据if(bytes[I]= = 0x 00 & & bytes[I+1]= = 0x 00 & & bytes[I+2]= = 0x 00 & & bytes[I+3])。} } return-1;} private byte[]getBytes(String video path)抛出io exception { InputStream is = new data InputStream(new File InputStream(new File(video path)));int lenint size = 1024byte[]buf;ByteArrayOutputStream Bos = new ByteArrayOutputStream();buf =新字节[大小];while ((len = is.read(buf,0,size))!= -1) bos.write(buf,0,len);buf = Bos . tobytearray();返回buf}}2.3主活动代码如下:package com . zqfdev . h 264 dode demo;导入Android . content . pm . package manager;导入Android . OS . bundle;导入Android . OS . environment;导入Android . util . log;导入Android . view . surface holder;导入Android . view . surface view;导入Ja . io . file;导入androidx . annotation . nonnull;导入androidx . app compat . app . app compat activity;导入androidx . core . app . activity compat;公共类MainActivity扩展了app compat activity { private String[]permiss = { & # 34;Android . permission . write _ EXTERNAL _ STORAGE & # 34;, "Android . permission . read _ EXTERNAL _ STORAGE & # 34;};private h 264 decodeplay h 264 decodeplay;//private h 265 decodeplay h 265 decodeplay;私有字符串videoPath@ Override protected void onCreate(Bundle sedInstanceState){ super . onCreate(sedInstanceState);setContentView(r . layout . activity _ main);check permiss();init view();} private void checkPermiss(){ int code = activity compat . checkselpermission(this,permiss[0]);如果(代码!= packagemanager。permission _ granted){//如果没有写权限,申请写权限,activitycompat。RequestPermissions (this,permission,11);} } private void init view(){ File dir = getExternalFilesDir(环境。目录_下载);如果(!dir . exists())dir . mkdirs();最终文件file =新文件(dir,& # 34;output.h264 & # 34);//最终文件file =新文件(dir,& # 34;output.h265 & # 34);如果(!file . exists()){ log . e(& # 34;标签& # 34;, "文件不存在& # 34;);返回;} video path = file . getabsolutepath();final surface view surface = findViewById(r . id . surface);final surface holder holder = surface . get holder();holder.addCallback(新的SurfaceHolder。callback(){ @ Override public void surface created(@ NonNull surface holder surface holder){ h 264 decode play = new h 264 decode play(video path,holder . get surface());h 264 decodeplay . decodeplay();//h 265 decodeplay = new h 265 decodeplay(video path,holder . get surface());//h 265 decodeplay . decodeplay();} @ Override public void surface changed(@ NonNull surface holder surface holder,int i,int i1,int I2){ } @ Override public void surface destroyed(@ NonNull surface holder surface holder){ } });}}测试的H264/H265视频可以通过FFmpeg提取得到。

命令行:

Ffmpeg -i源视频。MP4-编解码器复制-BSF: h264 _ MP4到NEXB-F H264输出视频。H264

本文主要介绍音视频媒体编解码的原理。以及MediaCodec解码H264和H265视频的主要讲解。音频和视频的技术点还是很多的。如果想进阶更多音视频技术,可以参考直传link.juejin.cn/?. Target = HTT…,点击查看获取想要学习的文档!

最后效果如下:

原文链接:音视频开发——媒体编解码解码H264/H265流视频——金块

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

发表回复

登录后才能评论