Android使用FFmpeg 解码H264并播放(二)

来源:互联网 发布:禅道的数据库配置 编辑:程序博客网 时间:2024/06/06 00:13

上一节记录了Android使用FFmpeg环境搭建过程。这一节记录视频解码过程。

问题描述

在开发中使用某摄像头的SDK,只能获取到一帧帧的 H264 视频数据,不知道视频流地址,需要自己解码出图像并播放。

问题解决

编译FFmpeg

点击查看

开发环境配置

点击查看

解码H264

原始数据格式

首先看我们能获取到数据格式

public class VideoStream{    //video buffer    byte[] streamBuffer;    //pps    byte[] ppsBuffer;    //sps    byte[] spsBuffer;    //当前是I帧还是P帧    int frameType; }

Java层代码

我们需要将从Java层取到的原始数据通过JNI传递到C层,交给FFmpeg解析。 Java 类大致如下:

public class H264FrameRender {    static {        //加载自己的 so 库        System.loadLibrary("decoder");    }    //保存C中的对象内存地址    private long nativeObject;        public H264FrameRender() {        long address = this._init();        if (address <= 0) {            throw new IllegalStateException("init failed");        }        this.nativeObject = address;    }    /**    * 将当前的buffer写入缓冲队列,等待解析    */    public void write(VideoStream videoStream) {        if (nativeObject <= 0) {            throw new IllegalStateException("H264FrameRender init failed,cannot decode ");        }        byte[] streamBuffer = videoStream.getmStreamBuffer();        byte[] spsBuffer = videoStream.getmSPSBuffer();        byte[] ppsBuffer = videoStream.getmPPSBuffer();        int spsLen = spsBuffer == null ? 0 : spsBuffer.length;        int ppsLen = ppsBuffer == null ? 0 : ppsBuffer.length;        boolean isIFrame = videoStream.getmType() == VideoStream.IFrameType;        _write(nativeObject, streamBuffer, streamBuffer.length, spsBuffer, spsLen, ppsBuffer,                ppsLen, isIFrame);    }    /**    * 释放内存,调用该方法后,将会释放C分配的内存空间,并将该Java对象标记为不可用    */      public void release() {        if (this.nativeObject > 0) {            _release(this.nativeObject);            this.nativeObject = 0;        }    }    /**     * 如果没有主动release,C 申请的空间将在Java类销毁时自动释放     *     * @throws Throwable     */    @Override    protected void finalize() throws Throwable {        release();        super.finalize();    }    /**    * 初始化内存空间,会调用C申请一段内存,并返回内存地址    */    private native long _init();    /**    * 将数据通过 JNI 交给 C 去解析    */    private native void _write(long nativeObject, byte[] streamBuffer, int length, byte[] spsBuffer,            int spsLen, byte[] ppsBuffer, int ppsLen, boolean isIFrame);}

JNI 代码

新建 h264_render.ch264_render.h ,添加两个JNI方法:

//h264_render.h#ifndef LITTLELF_JNI_H264_RENDER_H#define LITTLELF_JNI_H264_RENDER_H#include <jni.h>//Java: _init()JNIEXPORT jlong JNICALLJava_com_hencenx_littlelf_jni_H264FrameRender__1init(JNIEnv *env, jobject instance);//Java: _write(long nativeObject, long nativeObject, byte[] streamBuffer, int length, byte[] spsBuffer,int spsLen, byte[] ppsBuffer, int ppsLen, boolean isIFrame)JNIEXPORT void JNICALLJava_com_hencenx_littlelf_jni_H264FrameRender__1write(JNIEnv *env, jobject instance,                                                      jlong nativeObject, jbyteArray streamBuffer_,                                                      jint length, jbyteArray spsBuffer_,                                                      jint spsLen, jbyteArray ppsBuffer_,                                                      jint ppsLen, jboolean isIFrame);//Java: _release(long nativeObject)JNIEXPORT void JNICALLJava_com_hencenx_littlelf_jni_H264FrameRender__1release(JNIEnv *env, jobject instance,                                                        jlong nativeObject);#endif //LITTLELF_JNI_H264_RENDER_H

至此,我们可以在 C 层访问到流的 buffer 了。

FFmpeg 解析视频流

解码基本流程

FFmpeg 提供了一个解码和编码的 demo,代码很简单,精简后的代码如下:

//1.注册所有编解码器,注册后才能使用avcodec_register_all();//2.从注册的解码器里找到H264解码器AVCodec * codec = avcodec_find_decoder(AV_CODEC_ID_H264);//3. 初始化解码的上下文,上下文很关键,包含了解码所需要的信息AVCodecContext * ctx = avcodec_alloc_context3(codec);//准备一个容器用来装需要解码的原始H264数据AVPacket avpkt;//4. 准备一个容器用来装解码后的数据,AVFrame既可以表示视频数据,也可以表示音频数据AVFrame frame = av_frame_alloc();//5. 初始化avpkt,并将H264数据放进去(此处代码省略)//6. 初始化解码上下文,设置视频宽高等(因为可能是从I帧中获取的,所以写在这一步,此处代码省略)//7. 根据解码上下文打开解码器,这样解码器才算初始完毕,可以解码了avcodec_open(ctx, codec, NULL);//8. 解码 - 发送需要解码的数据给上下文avcodec_send_packet(ctx, avpkt);//9. 解码 - 从上下文中获取解码后的frame,解码完成avcodec_receive_frame(ctx, frame);
利用 sps 和 pps 初始化 上下文

我们的处理流程和 demo 类似,但稍有不同。因为解码H264必须提供视频的宽高,否则解析过程就会报错。 但是原始数据中并没有提供视频宽高。经过查阅得知,sps 和 pps 中就包含了视频宽高和其他一些解码必须的数据。我们需要将 sps 和 pps 放到AVCodecContext 的 extradata 中。
代码如下:

int extra_len = sps_len + pps_len;ctx->extradata = av_malloc(            sizeof(uint8_t) * (size_t) (extra_len + AV_INPUT_BUFFER_PADDING_SIZE));ctx->extradata_size = extra_len;memcpy(ctx->extradata, (uint8_t *) input_sps, (size_t) sps_len);memcpy(ctx->extradata + (size_t) sps_len, (uint8_t *) input_pps, (size_t) pps_len);            

经过以上处理,H264 解码就完成了。

阅读全文
1 0
原创粉丝点击