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.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 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
- Android使用FFmpeg 解码H264并播放(二)
- Android使用FFmpeg 解码H264并播放(一)
- Android使用FFmpeg 解码H264并播放(三)
- Android FFMpeg(三)——使用FFMpeg解码h264、aac
- 嵌入式linux------ffmpeg移植 解码H264(am335x解码H264到yuv420并通过SDL显示)
- 嵌入式linux------ffmpeg移植 解码H264(am335x解码H264到yuv420并通过SDL显示)
- ffmpeg解码jpg并编码成h264
- ffmpeg 接收h264+aac并解码
- ffmpeg解码jpg并编码成h264
- Android使用MediaCodec硬解码播放H264格式视频文件
- 【多媒体】Android使用MediaCodec硬解码播放H264格式视频文件
- Android使用MediaCodec硬解码播放H264格式视频文件
- 使用OpenAL和FFMPEG解码并播放音乐
- 使用jni调用ffmpeg.so中的H264解码函数播放文件
- h264码流分析(二)---以ffmpeg中h264解码为例
- android 4.4 H264 ffmpeg编解码
- 视频学习笔记:Android ffmpeg解码多路h264视频并显示
- 视频学习笔记:Android ffmpeg解码多路h264视频并显示
- 根据IP地址获取用户的MAC地址
- java数据库工具类
- python学习之if __name__ == 'main': 的作用和原理
- java多线程之生产者与消费者案例
- 九度1026:又一版 A+B
- Android使用FFmpeg 解码H264并播放(二)
- org.hibernate.ObjectNotFoundException: No row with the given identifier exists
- Smalidea+IntelliJ IDEA/Android Studio动态调试安卓app教程
- Android 自定义ViewGroup 实战篇 -> 实现FlowLayout
- python程序内存泄漏调试记录
- eclipse中一次部署多个项目
- LTP(Linux Test Project) for Android的编译
- Manifest merger failed with multiple
- 存储函数加上游标