使用FFMPEG类库分离出多媒体文件中的H.264码流
在使用FFMPEG的类库进行编程的过程中,可以直接输出解复用之后的的视频数据码流。只需要在每次调用av_read_frame()之后将得到的视频的AVPacket存为本地文件即可。
经试验,在分离MPEG2码流的时候,直接存储AVPacket即可。
在分离H.264码流的时候,直接存储AVPacket后的文件可能是不能播放的。
如果视音频复用格式是TS(MPEG2 Transport Stream),直接存储后的文件是可以播放的。
复用格式是FLV,MP4则不行。
经过长时间资料搜索发现,FLV,MP4这些属于“特殊容器”,需要经过以下处理才能得到可播放的H.264码流:
1.第一次存储AVPacket之前需要在前面加上H.264的SPS和PPS。这些信息存储在AVCodecContext的extradata里面。
并且需要使用FFMPEG中的名为"h264_mp4toannexb"的bitstream filter 进行处理。
然后将处理后的extradata存入文件
具体代码如下:(源码见最后)
FILE *fp=fopen("test.264","ab");
AVCodecContext *pCodecCtx=...
unsigned char *dummy=NULL; //输入的指针
int dummy_len;
AVBitStreamFilterContext* bsfc = av_bitstream_filter_init("h264_mp4toannexb");
av_bitstream_filter_filter(bsfc, pCodecCtx, NULL, &dummy, &dummy_len, NULL, , );
fwrite(pCodecCtx->extradata,pCodecCtx-->extradata_size,,fp);
av_bitstream_filter_close(bsfc);
free(dummy);
2.通过查看FFMPEG源代码我们发现,AVPacket中的数据起始处没有分隔符(0x00000001), 也不是0x65、0x67、0x68、0x41等字节,所以可以AVPacket肯定这不是标准的nalu。其实,AVPacket前4个字表示的是nalu的长度,从第5个字节开始才是nalu的数据。所以直接将AVPacket前4个字节替换为0x00000001即可得到标准的nalu数据。
具体代码如下:
char nal_start[]={,,,};
fwrite(nal_start,,,fp);
fwrite(pkt->data+,pkt->size-,,fp);
fclose(fp);
经过以上两步处理之后,我们就得到了可以正常播放的H.264码流。
3.ffmpeg中提供了一个流过滤器"h264_mp4toannexb"完成这项工作(从extradata中解析出sps及pps),关键代码如下:
//h264_mp4toannexb_bsf.c
static int h264_mp4toannexb_filter(AVBitStreamFilterContext *bsfc,
AVCodecContext *avctx, const char *args,
uint8_t **poutbuf, int *poutbuf_size,
const uint8_t *buf, int buf_size,
int keyframe) {
H264BSFContext *ctx = bsfc->priv_data;
uint8_t unit_type;
int32_t nal_size;
uint32_t cumul_size = ;
const uint8_t *buf_end = buf + buf_size; /* nothing to filter */
if (!avctx->extradata || avctx->extradata_size < ) {
*poutbuf = (uint8_t*) buf;
*poutbuf_size = buf_size;
return ;
} //
//从extradata中分析出SPS、PPS
//
/* retrieve sps and pps NAL units from extradata */
if (!ctx->extradata_parsed) {
uint16_t unit_size;
uint64_t total_size = ;
uint8_t *out = NULL, unit_nb, sps_done = , sps_seen = , pps_seen = ;
const uint8_t *extradata = avctx->extradata+; //跳过前4个字节
static const uint8_t nalu_header[] = {, , , }; /* retrieve length coded size */
ctx->length_size = (*extradata++ & 0x3) + ; //用于指示表示编码数据长度所需字节数
if (ctx->length_size == )
return AVERROR(EINVAL); /* retrieve sps and pps unit(s) */
unit_nb = *extradata++ & 0x1f; /* number of sps unit(s) */
if (!unit_nb) {
goto pps;
} else {
sps_seen = ;
} while (unit_nb--) {
void *tmp; unit_size = AV_RB16(extradata);
total_size += unit_size+;
if (total_size > INT_MAX - FF_INPUT_BUFFER_PADDING_SIZE ||
extradata++unit_size > avctx->extradata+avctx->extradata_size) {
av_free(out);
return AVERROR(EINVAL);
}
tmp = av_realloc(out, total_size + FF_INPUT_BUFFER_PADDING_SIZE);
if (!tmp) {
av_free(out);
return AVERROR(ENOMEM);
}
out = tmp;
memcpy(out+total_size-unit_size-, nalu_header, );
memcpy(out+total_size-unit_size, extradata+, unit_size);
extradata += +unit_size;
pps:
if (!unit_nb && !sps_done++) {
unit_nb = *extradata++; /* number of pps unit(s) */
if (unit_nb)
pps_seen = ;
}
} if(out)
memset(out + total_size, , FF_INPUT_BUFFER_PADDING_SIZE); if (!sps_seen)
av_log(avctx, AV_LOG_WARNING, "Warning: SPS NALU missing or invalid. The resulting stream may not play.\n");
if (!pps_seen)
av_log(avctx, AV_LOG_WARNING, "Warning: PPS NALU missing or invalid. The resulting stream may not play.\n"); av_free(avctx->extradata);
avctx->extradata = out;
avctx->extradata_size = total_size;
ctx->first_idr = ;
ctx->extradata_parsed = ;
} *poutbuf_size = ;
*poutbuf = NULL;
do {
if (buf + ctx->length_size > buf_end)
goto fail; //buf为NULL时,以下代码将不再执行 //
//用于保存数据长度的字节数,是在分析原extradata计算出来的
//
if (ctx->length_size == ) {
nal_size = buf[];
} else if (ctx->length_size == ) {
nal_size = AV_RB16(buf);
} else
nal_size = AV_RB32(buf); buf += ctx->length_size;
unit_type = *buf & 0x1f; if (buf + nal_size > buf_end || nal_size < )
goto fail; /* prepend only to the first type 5 NAL unit of an IDR picture */
if (ctx->first_idr && unit_type == ) {
//
//copy IDR 帧时,需要将sps及pps一同拷贝
//
if (alloc_and_copy(poutbuf, poutbuf_size,
avctx->extradata, avctx->extradata_size,
buf, nal_size) < )
goto fail;
ctx->first_idr = ;
} else {
//
//非IDR帧,没有sps及pps
if (alloc_and_copy(poutbuf, poutbuf_size,
NULL, ,
buf, nal_size) < )
goto fail;
if (!ctx->first_idr && unit_type == )
ctx->first_idr = ;
} buf += nal_size;
cumul_size += nal_size + ctx->length_size;
} while (cumul_size < buf_size); return ; fail:
av_freep(poutbuf);
*poutbuf_size = ;
return AVERROR(EINVAL);
}
一般情况下,extradata中包含一个sps、一个pps 的nalu, 从上面的代码中容易看出extradata的数据格式。分析后的sps及pps依然储存在extradata域中,并添加了起始符。从代码中还可以看出,上面的函数会将sps、pps及packet中的数据,都copy到poutbuf指示的内存中,如果不需要copy到指定内存,直接给buf参数传入空值即可。
使用FFMPEG类库分离出多媒体文件中的H.264码流的更多相关文章
- (转)使用FFMPEG类库分离出多媒体文件中的H.264码流
出自:http://blog.csdn.net/leixiaohua1020/article/details/11800877 在使用FFMPEG的类库进行编程的过程中,可以直接输出解复用之后的的 ...
- 海思3518EV200 SDK中获取和保存H.264码流详解
/****************************************** step 2: Start to get streams of each channel. ********** ...
- 最简单的基于FFMPEG的视频编码器(YUV编码为H.264)
本文介绍一个最简单的基于FFMPEG的视频编码器.该编码器实现了YUV420P的像素数据编码为H.264的压缩编码数据.编码器代码十分简单,可是每一行代码都非常重要,适合好好研究一下.弄清楚了本代码也 ...
- 我的Java开发学习之旅------>工具类:Java使用正则表达式分离出字符串中的中文和英文
今天看到一个工具类使用正则表达式将一大段字符串中的中文和英文都分离出来了,在此记录一下,读者可以收藏! import java.util.ArrayList; import java.util.Col ...
- FFmpeg的H.264解码器源代码简单分析:解析器(Parser)部分
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- FFmpeg的H.264解码器源代码简单分析:宏块解码(Decode)部分-帧间宏块(Inter)
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- FFmpeg的H.264解码器源代码简单分析:熵解码(Entropy Decoding)部分
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- FFmpeg的H.264解码器源代码简单分析:概述
===================================================== H.264源代码分析文章列表: [编码 - x264] x264源代码简单分析:概述 x26 ...
- FFmpeg的H.264解码器源代码简单分析
本文简单记录FFmpeg中libavcodec的H.264解码器(H.264 Decoder)的源代码.这个H.264解码器十分重要,可以说FFmpeg项目今天可以几乎“垄断”视音频编解码技术,很大一 ...
随机推荐
- PHP Composer 包管理器的安装和使用
一,包管理器的安装 01,首先确定 php 命令能够全局使用,要是不能请修改环境变量 02,使用 curl 命令安装 => curl -sS http://install.phpcomposer ...
- ubuntu16.04搭建geodjango+postgresql+postgis的WebGIS框架(三)加载空间数据
之前两篇基本上搭好了geodjango开发的环境了,当然你的电脑上肯定要有python和django的环境(这个我就不介绍了,网上一搜一大堆),我自己用的python3.5和django2.0(毕竟2 ...
- Java并发:线程间数据传递和交换
转自:https://www.cnblogs.com/java-zzl/p/9741288.html 一.通过SynchronousQueue方式实现线程间数据传递: 线程A与线程B共同持有一个Syn ...
- guava 对集合的支持
- UI5-学习篇-6-SAP创建OData服务-RFC
1.创建项目 2.Import RFC接口 3.定义实体名 目标服务器:若连接外部服务器则需SM59配置Destination 选择RFC函数名 4.选择数据源参数 5.设置主键值 6.保存成功 7. ...
- 使用Docker搭建Tomcat运行环境
1 准备宿主系统 准备一个 CentOS 7操作系统,具体要求如下: 必须是 64 位操作系统 建议内核在 3.8 以上 通过以下命令查看您的 CentOS 内核: # uname -r 2 安装Do ...
- ReactiveX 学习笔记(16)RxPY
RxPY RxPY 是 ReactiveX 的 Python语言实现. # 安装 RxPY $ pip3 install rx Successfully installed rx-1.6.1 Basi ...
- 侧边栏收起展开效果,onmouseover,onmouseout
//方法一<!doctype html> <html lang="en"> <head> <meta charset="UTF- ...
- 在服务器上同时启动多个tomcat
我所用Tomcat服务器都为zip版,非安装版.以两个为例:安装第二个Tomcat完成后,到安装目录下的conf子目录中打开server.xml文件,查找以下三处:(1) 修改http访问端口(默认为 ...
- Win8系统本地连接显示为网络2
Win8系统中,当改变了网络环境,本地连接就会被识别为网络2,网络3等: 如果在一个固定的网络环境中,需要修改此名称,可以打开注册表: [HKEY_LOCAL_MACHINE\SOFTWARE\Mic ...