Android上用MediaCodec+SurfaceView的方式对H264裸流解码播放

    公司要实现在手机app上查看到摄像头的拍摄视频,使用了第三方的服务,但第三方最终只会底层透传一个视频的字节数组,并没有视频播放的功能,此功能需要自己实现。

    由于第三方服务是基于native的,所以之前写的h5app不能用了,得重新用native实现,而我本人也是不懂安卓的,但没办法,还是得去琢磨。

    一开始只知道我现在能拿到视频的字节数组,但压根不知道怎么把数组变成视频显示在手机上。后来有人告诉我,这个字节数组是H264码,可以查查H264,看有没有突破口。我查了一下,确实有,android有一个叫MediaCodec的东西,它可以形成一个解码器,然后就在网上摸索了一番,大致思路有了。

    (1)android播放的控件叫SurfaceView,是可以在布局文件中定义的

    (2)通过SurfaceView可以拿到SurfaceHolder,继而拿到Surface

    (3)可以把Surface配置在MediaCodec中,从而页面和解码器就关联起来了

    (4)然后用这么一个解码器去处理那个字节数组,就能显示视频了

    下来记录一下遇到的问题以及解决方法

    (1)MediaCodec的定义


    (2)MediaCodec.configure()的4个参数


    第一个参数MediaFormat,自己单独定义即可

    第二个参数Surface,这个就坑了,看了许多博客,上边直接就是一个变量的名字。。。鬼知道这个变量是怎么定义的,反正找呀找,终于找到个看上去能试的,就试了试,可以。SurfaceView.getSurfaceHolder().getSurface()

    第三个参数crypto,本身就没学过安卓,也就不想深究了,能实现就行,博客上都写的null,我也就写null了

    第四个参数flags,也是博客上都是0,我也就写0了

    (3)MediaCodec的使用

    如果想深究的话,可以看看其完整概念和源码。我是直接从网上copy了一个方法,发现可以直接用,就直接用了。。。


    没想太多,直接用了,尝试性改了些不痛不痒的东西。然后播放视频的线程叫VideoThread,里边有个while(true)的循环,这个循环每次都能拿到一个字节数组,然后我就在拿到之后调用了这个onFrame()方法,就把视频显示出来了。


    接下来说一下坑。。。

    视频是能播放了,但是他播放的形式是在主Activity里边启动一个线程,这个线程又会让播放视频的线程join进来

(1)MediaCodec的Configure()和Start()必须写在SurfaceHolder的回掉函数中,否则会出现surface还没create出来的时候mediaCodec就要去配置它,从而出错的问题


(2)线程。。。

    在视频播放的时候,用户点击back键或者类似任务管理器的那个键的时候,主页面都没了,那我启动的线程该咋弄呢。。。

线程是可以停的,让他自己停,即便是while(true),遇到break不就结束了么


    只要用户执行上述行为,onFrame()绝对会出现问题,catch一下break一下就完了嘛

(3)终极大问题,我可以点击back再进来视频可以重新开始,但如果点击类似任务管理器的那个键,再在那里边点这个app,程序就直接崩了


    查了很多,也试了很多,比如最终我把MediaCodec,MediaFormat等这些变量全定义在线程里边,改写SurfaceHolder回调函数中的surfaceDestroyed()方法(MediaCodec的release stop reset什么的,试了试,唉。。。关他鸟事)以及其他记不起来的尝试,都不行,都会出现上图中的FATAL EXCEPTION(致命错误),我不理解为啥back再进来正常,通过任务管理器就不行了呢,最后我发现back再进来是从onCreate开始的,而后者,onRestart,呵呵。。。可能是View的问题,我就想着,原来那个View不能用了?尝试了一下finish,挺笨的。。确实不好,再点回来是上个界面。。。

    我也尝试改写整个Activity的onResume方法,其实这里已经靠近行得通的解决方案了,设个flag,执行完onCreate就是false,执行过onResume,就最后设为true,每次onResume都先看看是不是true,其实也就第一次不是,进行差别处理,在if里边我重新执行线程,看了看,还是不行,还是上图那个错误,就是这个surfaceView的问题,老的不能用,新的是啥啊。。。等等,新的?我尝试性的setContentView了一下,居然,终于好了,不崩也不报错了,视频也有显示了。(这里边少一行都不行的)


    还是没学过android,现在也没工夫从下到上去学了,我得出一个结论,貌似setContentView就相当于全部刷新了,之前的可能就全删除了,正好符合我的要求。就这个问题,困扰了我两天半,虽说最终解决方案看似如此简单,可要找到这个解决方案的过程可是不轻松,虽说很多尝试过的方案都没行得通,但也对相关知识了解的差不多了。

    应该也不会有什么人看,暂且记录一下心路历程,问题解决额一瞬间其实很多东西都记不清了,所以写的也就乱了,也就少了点内容。思路是清晰,并且看上去也是可行的,但跟过程,绝对不是一个等级的。至于相关的MediaPlayer,FFmpeg什么的,以后有机会接触到再去主动碰吧。

 

  • 7
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
很抱歉,Android系统不支持 VAAPI,它是用于 Linux 等操作系统的视频加速 API。在 Android 中,可以使用硬件加速来加速视频播放,例如使用 MediaCodecSurfaceView 进行视频解码和渲染。下面是一个简单的示例代码: ```java import android.media.MediaCodec; import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaCodec.BufferInfo; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; import java.io.IOException; import java.nio.ByteBuffer; public class VideoPlayer implements SurfaceHolder.Callback { private SurfaceView mSurfaceView; private Surface mSurface; private MediaExtractor mExtractor; private MediaCodec mDecoder; private boolean mIsPlaying; public VideoPlayer(SurfaceView surfaceView) { mSurfaceView = surfaceView; mSurfaceView.getHolder().addCallback(this); } public void play(String videoPath) throws IOException { mExtractor = new MediaExtractor(); mExtractor.setDataSource(videoPath); int trackIndex = selectTrack(mExtractor); mExtractor.selectTrack(trackIndex); MediaFormat mediaFormat = mExtractor.getTrackFormat(trackIndex); String mime = mediaFormat.getString(MediaFormat.KEY_MIME); mDecoder = MediaCodec.createDecoderByType(mime); mDecoder.configure(mediaFormat, mSurface, null, 0); mDecoder.start(); ByteBuffer[] inputBuffers = mDecoder.getInputBuffers(); ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers(); BufferInfo bufferInfo = new BufferInfo(); mIsPlaying = true; while (mIsPlaying) { int inputBufferIndex = mDecoder.dequeueInputBuffer(10000); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; int sampleSize = mExtractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { mDecoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); mIsPlaying = false; } else { long presentationTimeUs = mExtractor.getSampleTime(); mDecoder.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0); mExtractor.advance(); } } int outputBufferIndex = mDecoder.dequeueOutputBuffer(bufferInfo, 10000); switch (outputBufferIndex) { case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: outputBuffers = mDecoder.getOutputBuffers(); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // do nothing break; case MediaCodec.INFO_TRY_AGAIN_LATER: // do nothing break; default: ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; mDecoder.releaseOutputBuffer(outputBufferIndex, true); break; } } mDecoder.stop(); mDecoder.release(); mExtractor.release(); } private int selectTrack(MediaExtractor extractor) { int numTracks = extractor.getTrackCount(); for (int i = 0; i < numTracks; i++) { MediaFormat format = extractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); if (mime.startsWith("video/")) { return i; } } return -1; } @Override public void surfaceCreated(SurfaceHolder holder) { mSurface = holder.getSurface(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // do nothing } @Override public void surfaceDestroyed(SurfaceHolder holder) { mSurface = null; } } ``` 使用示例: ```java VideoPlayer player = new VideoPlayer(surfaceView); try { player.play(videoPath); } catch (IOException e) { e.printStackTrace(); } ```
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值