Android使用FFmpeg 解码H264并播放(二)
2017-06-12 17:19
555 查看
上一节记录了Android使用FFmpeg环境搭建过程。这一节记录视频解码过程。
至此,我们可以在 C 层访问到流的 buffer 了。
FFmpeg 提供了一个解码和编码的 demo,代码很简单,精简后的代码如下:
利用 sps 和 pps 初始化 上下文
我们的处理流程和 demo 类似,但稍有不同。因为解码H264必须提供视频的宽高,否则解析过程就会报错。 但是原始数据中并没有提供视频宽高。经过查阅得知,sps 和 pps 中就包含了视频宽高和其他一些解码必须的数据。我们需要将 sps 和 pps 放到AVCodecContext 的 extradata 中。
代码如下:
经过以上处理,H264 解码就完成了。
问题描述
在开发中使用某摄像头的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.c 和 h264_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 JNICALL Java_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 JNICALL Java_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 JNICALL Java_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 解码就完成了。
相关文章推荐
- Android使用FFmpeg 解码H264并播放(三)
- Android使用FFmpeg 解码H264并播放(一)
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- 使用jni调用ffmpeg.so中的H264解码函数播放文件
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- Android FFMpeg(三)——使用FFMpeg解码h264、aac
- Android平台上使用SDL官方demo播放视频(使用ffmpeg最新版解码)
- ffmpeg将yuv编码成h264数据量变小且可以使用暴风影音播放出来,很短一闪而过
- linux下mplayer(ffmpeg)通过x264解码播放高清h264视频
- [置顶] Android使用MediaCodec硬解码播放H264格式视频文件
- 在iOS平台使用ffmpeg解码h264视频流
- Android+FFmpeg+OpenSL ES音频解码播放
- Live555接收h264使用ffmpeg解码为YUV420 .
- Android利用mediacodec进行视频H264编码解码播放
- Android 解码MediaCodec 播放H264 265
- Android+FFmpeg+ANativeWindow视频解码播放
- 在iOS平台使用ffmpeg解码h264视频流
- android 4.4 H264 ffmpeg编解码
- Live555接收h264使用ffmpeg解码为YUV420