Android使用FFmpeg 解码H264并播放(二)
上一节记录了Android使用FFmpeg环境搭建过程。这一节记录视频解码过程。
问题描述
在开发中使用某摄像头的SDK,只能获取到一帧帧的 H264 视频数据,不知道视频流地址,需要自己解码出图像并播放。
问题解决
编译FFmpeg
点击查看
开发环境配置
点击查看
解码H264
原始数据格式
首先看我们能获取到数据格式
public class VideoStream{//video bufferbyte[] streamBuffer;//ppsbyte[] ppsBuffer;//spsbyte[] 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*/@Overrideprotected 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并播放(二)相关推荐
- ffmpeg 切片花屏_利用ffmpeg解码H264,花屏,该如何解决
利用ffmpeg解码H264,花屏 UINT CMP4File::VideoCap_Thread_Fun(void* pParam) { CMP4File *pMP4File=(CMP4File*) ...
- ffmpeg解码H264缺少帧的解决办法
最近用ffmpeg解码H264裸码流文件,发现解码总是少几帧.上网查了些资料,解决了. 当使用avcodec_decode_video2时,如果第三个参数的值为1,则表示完成一帧的解码,如果为0,表示 ...
- 使用FFMPEG解码和OpenAL播放音乐
使用FFMPEG解码和OpenAL播放音乐 OpenAL是一个开源的音效库,然而这里只用它来播放音乐. FFMPEG负责把音乐解码并转换成指定的编码和采样率,然后送到OpenAL中播放. (已在w ...
- android下实时传输h264并播放
这周给安排了个任务,在android端读取H264原始视频数据,传一帧播一帧,播就播吧,还要Socket实时传输实时播,虽然并没有接触过,都无从下手,不过想到直播都是这么个形式,说明肯定是行得通的,而 ...
- Android使用MediaCodec解码H264视频解码器
前些日子有写了一篇博客[Android使用MediaCodec硬解码播放H264格式视频文件](http://blog.csdn.net/true100/article/details/5399293 ...
- (转)FFMPEG解码H264拼帧简解
http://blog.csdn.net/ikevin/article/details/7649095 H264的I帧通常 0x00 0x00 0x00 0x01 0x67 开始,到下一个帧头开始之前 ...
- FFmpeg解码H264裸流并转换成opencv Mat
感谢雷霄骅博士的在中文视频编解码的付出,http://blog.csdn.net/leixiaohua1020 最近要搞一些视频推流的事情,要解析H264裸流并且获取opencv格式的Mat数据给算法 ...
- android ffmpeg 解码,如何在Android用FFmpeg解码图像
白猪掌柜的 创建一个VideoPicture结构体用来保存解码出来的图像./** SDL_Lesson.c** Created on: Aug 12, 2014* Author: clarck*/#i ...
- FFmpeg解码的全能播放器
http://www.eoeandroid.com/thread-53373-1-1.html
- 使用ffmpeg解码音频sdl(push)播放
自定义播放器系列 第一章 视频渲染 第二章 音频(push)播放(本章) 第三章 音频(pull)播放 第四章 实现时钟同步 第五章 实现通用时钟同步 第六章 实现播放器 文章目录 自定义播放器系列 ...
最新文章
- shell查看并修复网络连接
- linux搭建--centos使用qemu-kvm,libvirt搭建虚拟机,并搭建libvmi来虚拟机自省(四)
- 滚动条禁止_Axure 教程:不可见滚动条的页面滚动效果
- 其实你女朋友也不是很爱你...
- Python稳基修炼的经典案例7(计算机二级、初学者必须掌握的例题)
- 几行Python代码生成饭店营业额模拟数据并保存为CSV文件
- xp下固态硬盘测试软件,XP真的老矣?SSD实战XP/Win7/Win8系统
- 购买域名和个人网站备案流程
- 命令行查看ubuntu版本号
- 这些年,这些ACM大佬-吴作凡访谈
- 原神紫晶块采集点位置在哪 紫晶块采集点路线图详情
- Mocha.js官方文档翻译 —— 简单、灵活、有趣
- 【Pygame小游戏】这款“打地鼠”小游戏要火了(来来来)
- 跟我学RocketMQ之批量消息发送源码解析
- Visual Studio2022 运行代码时“发生生成错误,是否继续并运行上次的成功生成”
- OKHTTP和retrofit 网络框架集成的有https验证的APP破解抓包
- 被缠上了,小王问我怎么在 Spring Boot 中使用 JDBC 连接 MySQL
- matlab中的measure,Matlab中 awgn 函数输入参数带有‘measured’ 时snr的含义
- 人类发明技术本来是为了便利,但最终自己的生活却被技术无情地改变甚至主宰了
- Mybatis 查询 返回值中只有id有值,其他都是null;