#ffmpeg/音视频抽取和转换#
音频抽取
- av_init_packet
- av_find_best_steam在多媒体里面找到最好的流
- av_read_frame获取数据包,这里之所以用frame而不用packet,就是因为在早期版本里面,解码前的帧和解码后的帧都是frame.所以现在就没改。
- av_packet_unref 每次在read_frame读取数据包的时候,会增加数据包引用计数(加一),如果为0,那么包资源就会被释放。
源码如下:
#include "stdafx.h"
#define __STDC_CONSTANT_MACRO
extern "C"
{
#include "libavformat\avformat.h"
#include "libavutil\log.h"
}
/*--错误数值转错误字符--*/
char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) av_make_error_string(av_error,AV_ERROR_MAX_STRING_SIZE,errnum)
int main(int argc, char *argv[]) {
int ret;
int audioIndex; //多媒体中,第一路流为音频,那么index就是0,如果第二路就是1,以此类推
int len; //写入的值
char *filePath = "input.mp4"; //输入文件名
char *outputPath = "output.aac"; //输出文件名
AVFormatContext *fmtCtx = NULL;
AVPacket pkt ;
FILE *fdOutput=NULL;
/*--打开文件--*/
ret = avformat_open_input(&fmtCtx, filePath, NULL, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Open file failed: %s\n", av_err2str(ret));
goto end;
}
/*--打印文件信息--*/
av_dump_format(fmtCtx, 0, filePath, 0);
av_log(NULL, AV_LOG_INFO, "dump format success\n" );
/*--打开输出文件--*/
fdOutput = fopen(outputPath, "wb+");
if (!fdOutput) {
av_log(NULL, AV_LOG_ERROR, "file open error %s\n", fdOutput);
goto end;
}
/*--找到合适的流--*/
ret = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Find stream failed: %s\n", av_err2str(ret));
goto end;
}
audioIndex = ret; //获取到当前流的流index
/*--初始化包--*/
av_init_packet(&pkt);
/*--开始从流里读包--*/
while (av_read_frame(fmtCtx, &pkt) >= 0) {
if (pkt.stream_index == audioIndex) { //读到的流是否为找到最合适的流
len = fwrite(pkt.data, 1, pkt.size, fdOutput); //数据写入aac文件
if (len != pkt.size) { //如果数据写入的不正确
av_log(NULL, AV_LOG_WARNING, "Wanning: length of write data not equal to pkt size: %s\n", av_err2str(ret));
}
}
/*--再把pkt里data给释放--*/
av_packet_unref(&pkt);
}
end:
fclose(fdOutput);
avformat_close_input(&fmtCtx);
return 0;
}
播放及解决
使用 ffmpeg播放aac的时候,由于是裸数据,肯定是播不出来的。需要给这些数据加adts头,包含采样率等信息。
头是怎么搞出来的以后再搞。
添加头,在每帧数据的头部,需要加上7字节的adts头。
头代码代码如下:
void adts_header(char *szAdtsHeader, int dataLen) {
int audio_object_type = 2;
int sampling_frequency_index = 7;
int channel_config = 2;
int adtsLen = dataLen + 7;
szAdtsHeader[0] = 0xff; //syncword:0xfff 高8bits
szAdtsHeader[1] = 0xf0; //syncword:0xfff 低4bits
szAdtsHeader[1] |= (0 << 3); //MPEG Version:0 for MPEG-4,1 for MPEG-2 1bit
szAdtsHeader[1] |= (0 << 1); //Layer:0 2bits
szAdtsHeader[1] |= 1; //protection absent:1 1bit
szAdtsHeader[2] = (audio_object_type - 1) << 6; //profile:audio_object_type - 1 2bits
szAdtsHeader[2] |= (sampling_frequency_index & 0x0f) << 2; //sampling frequency index:sampling_frequency_index 4bits
szAdtsHeader[2] |= (0 << 1); //private bit:0 1bit
szAdtsHeader[2] |= (channel_config & 0x04) >> 2; //channel configuration:channel_config 高1bit
szAdtsHeader[3] = (channel_config & 0x03) << 6; //channel configuration:channel_config 低2bits
szAdtsHeader[3] |= (0 << 5); //original:0 1bit
szAdtsHeader[3] |= (0 << 4); //home:0 1bit
szAdtsHeader[3] |= (0 << 3); //copyright id bit:0 1bit
szAdtsHeader[3] |= (0 << 2); //copyright id start:0 1bit
szAdtsHeader[3] |= ((adtsLen & 0x1800) >> 11); //frame length:value 高2bits
szAdtsHeader[4] = (uint8_t)((adtsLen & 0x7f8) >> 3); //frame length:value 中间8bits
szAdtsHeader[5] = (uint8_t)((adtsLen & 0x7) << 5); //frame length:value 低3bits
szAdtsHeader[5] |= 0x1f; //buffer fullness:0x7ff 高5bits
szAdtsHeader[6] = 0xfc;
}
mp4文件抽取h264数据
h264两种封装格式
h264有两种封装格式:①annexb 传统模式,有startcode,es中有sps和pps。②mp4模式,没有startcode,sps/pps被封装在容器里面,每一帧前是这个帧的长度。
解码器一般只支持annexb模式,转换模式在ffmpeg里面用h264_mp4toannexb_filter
。
annexb数据在包中的存储方式如下
start-code[4字节] | 1字节数据,后五位表示帧类型 | sps/pps数据 | 实际数据 |
startcode[3字节] | 1字节数据,后五位表示帧类型 | 实际数据 | |
startcode[3字节] | 1字节数据,后五位表示帧类型 | 实际数据 | 。。。。 |
- start code:在处理视频信息的时候,在每一帧的视频数据前面,有个信息头。或者有个特征码,来标志这一帧开始上一帧结束。这样形成的文件才可以被播放器播放。对于sps/pps,特征码就是4个字节,0x00000001,对于非sps/pps,就是3个字节。0x0000001。
- SPS/PPS: 解码视频的参数,视频分辨率帧率等。一个普通视频中,只需要一个SPS/PPS即可。关键帧前面必要要有这俩。
- 视频编码成的包叫NALU,NAL,网络抽象层单元(Network abstraction layer units),
- 一个包里,如果帧很小,那就很多帧,如果帧大,那么一包里面可能就1帧。而pkt里面每一帧的开头,有四字节(32位)数据来表示帧的大小。获取到这个帧的大小,就可以得到这个帧的数据有多大。
这个表示帧大小的4字节数据,存储方式应该是小端,高字节的8位在后,低字节在前(实际验证了确实是这样),那么要得到这个帧的大小,需要一个4次的循环,第一次取出pkg->data前八位,左移8位,再从pkg->data中得到第2个字节的内容,再左移八位。。。 - 帧实际数据的第一个字节为nal单元,该字节的后五位,其实就是nal单元类型,sps为7,pps为8,关键帧为5,非关键帧为1。
代码示例
函数分析如下:
代码:
#include "stdafx.h"
#define __STDC_CONSTANT_MACRO
extern "C"
{
#include "libavformat\avformat.h"
#include "libavutil\log.h"
}
/*--错误数值转错误字符--*/
char av_error[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) av_make_error_string(av_error,AV_ERROR_MAX_STRING_SIZE,errnum)
/*--设置SPSPPS的特征码宏--*/
#define AV_SET_SPSPPS_STARTCODE(p,val) do{\
uint32_t d = (val); \
((uint8_t*)(p))[3] = (d); \
((uint8_t*)(p))[2] = (d)>>8;\
((uint8_t*)(p))[1] = (d)>>16;\
((uint8_t*)(p))[0] = (d)>>24;\
}while(0)
/*--------------------------------------------取出前两字节的值--------------------------------------*/
#define AV_RB16(x) \
(((const uint8_t*)(x))[0] <<8 | ((const uint8_t*)(x))[1])
/*--------------------------------------------复制帧数据,sps/pps数据,添加特征码--------------------------------------*/
static int copy_alldata_2packet(AVPacket *out, const uint8_t *sps_pps, uint32_t sps_pps_size,
const uint8_t *in, uint32_t in_size)
{
/*--确定特征码大小--*/
uint32_t offset = out->size;
uint8_t nal_header_size = offset ? 3 : 4; //如果数据没有偏移量,说明下面的数据为sps/pps,sps/pps为4字节
int err;
err = av_grow_packet(out, sps_pps_size+ in_size + nal_header_size); //传进来实际数据大小+特征码大小+sps/pps大小
if (err < 0)
return err;
if (sps_pps)
memcpy(out->data + offset, sps_pps, sps_pps_size);//把sps数据给拷贝进out data里面
memcpy(out->data + offset +nal_header_size+ sps_pps_size, in, in_size);//把sps和pps后面的数据拷贝进来
/*--如果out里面已经有数据了,那么就把特征码设置为0x000001
如果没有数据,那么就是sps/pps,特征码是4字节0x00000001--*/
if (!offset)
AV_SET_SPSPPS_STARTCODE(out->data + sps_pps_size, 1);
else
{
(out->data + offset + sps_pps_size)[0] = 0;
(out->data + offset + sps_pps_size)[1] = 0;
(out->data + offset + sps_pps_size)[2] = 1;
}
return 0;
}
/*--------------------------------------------给数据增加sps和pps数据--------------------------------------*/
int h264_extradata_2_annexb(const uint8_t *codec_extradata, const int codec_extradata_size, AVPacket *out_extradata, int padding)
{
uint16_t unit_size;
uint64_t total_size = NULL, unit_nb, sps_done = 0;
uint8_t *out = NULL, pps_seen = 0, sps_offset = 0, pps_offset = 0, sps_seen = 0;
const uint8_t *extradata = codec_extradata + 4; //实际数据
static const uint8_t nalu_header[4] = { 0,0,0,1 };
int length_size = (*extradata++ & 0x3) + 1; //第一个字节是表明sps和pps数据长度,一般是占2个字节
sps_offset = pps_offset = -1;
unit_nb = *extradata++ & 0x1f; //表示有多少sps和pps,一般extradata就1个
if (!unit_nb)
goto pps;
else
{
sps_offset = 0;
sps_seen = 1; //找到了sps
}
while (unit_nb--)
{
int err;
unit_size = AV_RB16(extradata); //取出两个字节。作为大小
total_size += unit_size + 4; //+4是因为sps和pps前面的特征码.
if (total_size > INT_MAX - padding) {
av_log(NULL, AV_LOG_ERROR,
"Too big extradata size, corrupted stream or invalid MP4/AVCC bitstream\n");
av_free(out);
return AVERROR(EINVAL);
}
if (extradata + 2 + unit_size > codec_extradata + codec_extradata_size) {
av_log(NULL, AV_LOG_ERROR, "Packet header is not contained in global extradata, "
"corrupted stream or invalid MP4/AVCC bitstream\n");
av_free(out);
return AVERROR(EINVAL);
}
if ((err = av_reallocp(&out, total_size + padding)) < 0)
return err;
memcpy(out + total_size - unit_size - 4, nalu_header, 4); //分配0x00000001
memcpy(out + total_size - unit_size, extradata + 2, unit_size); //复制spspps数据到out的数据区
extradata += 2 + unit_size; //把数据置尾
pps:
if (!unit_nb && !sps_done++) { //如果没有spspps单元且sps没有完成
unit_nb = *extradata++; /* number of pps unit(s) */
if (unit_nb) {
pps_offset = total_size;
pps_seen = 1;
}
}
}
if (out)
memset(out + total_size, 0, padding);
if (!sps_seen)
av_log(NULL, AV_LOG_WARNING,
"Warning: SPS NALU missing or invalid. "
"The resulting stream may not play.\n");
if (!pps_seen)
av_log(NULL, AV_LOG_WARNING,
"Warning: PPS NALU missing or invalid. "
"The resulting stream may not play.\n");
out_extradata->data = out;
out_extradata->size = total_size;
return length_size;
}
/*--------------------------------------------读取一个包里的帧数据到输出文件里面--------------------------------------*/
int h264_mp42annexb(AVFormatContext *fmtCtx, AVPacket *in, FILE *fd_dst) {
AVPacket *out = NULL;
AVPacket spspps_pkt; //spspps包
int len;
uint8_t unit_type;
int32_t nal_size;
const uint8_t *buf; //输入进来pkg的数据
const uint8_t *buf_end;
int buf_size;
int ret = 0, i;
out = av_packet_alloc();
buf = in->data;
buf_size = in->size; //获取数据大小
buf_end = buf + buf_size;
do {
ret = AVERROR(EINVAL);
if (buf + 4 > buf_end) //如果这个数据小于4字节,那么就报错
goto fail;
/*--获取帧大小--*/
for (nal_size = 0, i = 0; i < 4; i++)
nal_size = (nal_size << 8) | buf[i];
buf += 4; //前进4字节
unit_type = *buf & 0x1f; //获取单元类型
if (nal_size > buf_end - buf || nal_size < 0) //如果nalsiz大于buffer实际大小,那么这帧数据其实已经被损毁了
goto fail;
if (unit_type == 5) //如果是关键帧
{
/*--获取sps和pps数据--*/
h264_extradata_2_annexb(fmtCtx->streams[in->stream_index]->codec->extradata, fmtCtx->streams[in->stream_index]->codec->extradata_size, &spspps_pkt, AV_INPUT_BUFFER_PADDING_SIZE);
/*--为数据增加特征码--*/
if ((ret = copy_alldata_2packet(out, spspps_pkt.data, spspps_pkt.size, buf, nal_size)) < 0)
goto fail;
}
else
{
if ((ret = copy_alldata_2packet(out,NULL, 0, buf, nal_size)) < 0)
goto fail;
}
/*--数据输出到目标文件--*/
len = fwrite(out->data, 1, out->size, fd_dst);
if (len != out->size) //如果输出出错
av_log(NULL, AV_LOG_DEBUG, "fwrite is fail,len = %d ,out->size = %d\n ", len, out->size);
fflush(fd_dst); //从缓冲刷新到文件
} while ( 0 < buf_size);
fail:
av_packet_free(&out);
return ret;
}
int main(int argc, char *argv[]) {
int ret;
int len; //写入的值
int videoIndex = 0;
char *filePath = "input.mp4"; //输入文件名
char *outputPath = "output."; //输出文件名
AVFormatContext *fmtCtx = NULL;
AVPacket pkt ;
FILE *fdOutput=NULL;
av_log_set_level(AV_LOG_DEBUG);
/*--打开文件--*/
ret = avformat_open_input(&fmtCtx, filePath, NULL, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Open file failed: %s\n", av_err2str(ret));
goto end;
}
/*--打印文件信息--*/
av_dump_format(fmtCtx, 0, filePath, 0);
av_log(NULL, AV_LOG_INFO, "dump format success\n" );
/*--打开输出文件,为--*/
fdOutput = fopen(outputPath, "wb");
if (!fdOutput) {
av_log(NULL, AV_LOG_ERROR, "file open error %s\n", fdOutput);
goto end;
}
/*--找到合适的流--*/
ret = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Find stream failed: %s\n", av_err2str(ret));
goto end;
}
videoIndex = ret; //获取到当前流的流index
/*--初始化包--*/
av_init_packet(&pkt);
/*--开始从流里读包--*/
while (av_read_frame(fmtCtx, &pkt) >= 0) {
if (pkt.stream_index == videoIndex) { //读到的流是否为找到最合适的流
h264_mp42annexb(fmtCtx, &pkt, fdOutput); //格式转换,把mp4格式转换成播放器能放的annexb模式,并且输出到文件里面
}
}
/*--再把pkt里data给释放--*/
av_packet_unref(&pkt);
end:
fclose(fdOutput);
avformat_close_input(&fmtCtx);
return 0;
}
mp4转flv
函数
avformat_alloc_output_context2()
分配一个输出文件上下文,(之前有输入的上下文件格式)。
avformat_free_context()
avformat_new_stream()
生成新的多媒体文件里面,有很多流/轨,包含了字幕和视频音频轨。
avcodec_parameters_copy()
把对应的参数拷贝到新的流里面。
写入步骤
avformat_write_header()
写多媒体文件头
av_write_frame() /av_interleaved_write_frame()
一般用后者比较多,用的交叉写入数据
av_write_trailer()
写入尾部,在ffmpeg,有些视频是没有尾部的,但是在ffmpeg里面已经做了处理,没有尾部的写空。
一些概念
1. DTS PTS
I帧不需要依赖其他帧即可解码出完整图像
P帧需要依赖他前面的帧
B帧依赖排在后面或者前面的帧。
那么,由于视频流先到来的B帧无法立即解码,得等他依赖的后面I P帧解码完成,但是这样播放时间与解码时间不一致了。那么就引出了DTS和PTS。
比如播放帧顺序为 I B B P,解码B帧需要P帧信息,那么顺序就变成了IPBB。
DTS和PTS和指导播放端的行为,由编码器在编码时生成。DTS告诉我们应该按照什么顺序解码,PTS告诉我们按照什么顺序显示图像。
- DTS(Decoding Time Stamp):即解码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
- PTS(Presentation Time Stamp):即显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
对于音频,没有类似于I B P帧结构,不需要双向预测,所以音频帧的DTS、PTS顺序一致。那么同步就成了另外一个问题,不然音频视频就不同步了。
解决办法就是选择一个参考时钟,编码音视频的时候,根据参考时钟时间给每帧数据打上时间戳。播放时候读取一帧的时间戳,然后参考 当前 参考时钟上的时间来安排播放时间。由此又引出了三种播放方式:
-
同步视频到音频
-
同步音频到视频
-
同步音频视频到外部时钟
2. time_base刻度问题
在获取每一帧数据的时候,音频流采样是441000,那么刻度就是1/441000,而av_read_frame
读取的一包数据输出默认是1000的刻度,不同的刻度之间如果不转换,音视频播放如果刻度不同步,就会出现播放不同步的问题。需要利用av_rescale_q_rnd
来进行转换。
av_rescale_rnd函数
@param int64_t a
@param int64_t b__
@ param int64_t c
@ param enum AVRounding rnd
作用就是计算a*b/c,然后利用rnd这个参数来选择取舍方式。
AVRounding 有五种方式
-
AV_ROUND_ZERO 0趋近于0
-
AV_ROUND_INF 1趋远于0
-
AV_ROUND_DOWN 2趋向于更小的整数
-
AV_ROUND_UP 3趋向于更大的整数
-
AV_ROUND_NEAR_INF 4四舍五入
-
AV_ROUND_PASS_MINMAX 8192 0x2000不像其他的几个枚举,这个值相当于一个bitmask,必须要和其他的枚举值一起用。头文件里面举例,用了
-
av_rescale_rnd(3,1,2,AV_ROUND_UP|AV_ROUND_PASS_MINMAX) 3*1/2 =>round up =>2 av_rescale_rnd(AV_NOPTS_VALUE, 1, 2, AV_ROUND_UP | AV_ROUND_PASS_MINMAX); Rescaling AV_NOPTS_VALUE: AV_NOPTS_VALUE == INT64_MIN AV_NOPTS_VALUE is passed through => AV_NOPTS_VALUE
代码
/*------------------------------------------------------------------------------------
* 把mp4格式的文件转换成flv格式的
*
*------------------------------------------------------------------------------------*/
#include "stdafx.h"
#define __STDC_CONSTANT_MACRO
extern "C"
{
#include "libavformat\avformat.h"
#include "libavutil\log.h"
#include "libavutil\timestamp.h"
}
/*--------------------------------------------宏--------------------------------------*/
/*--错误值转错误自符--*/
char ERR_BUF[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) av_make_error_string(ERR_BUF,AV_ERROR_MAX_STRING_SIZE,errnum)
char TSBUF[AV_TS_MAX_STRING_SIZE] = { 0 };
#define av_ts2str(ts) av_ts_make_string(TSBUF,ts)
char TS2TIME_BUF[AV_TS_MAX_STRING_SIZE] = { 0 };
#define av_ts2timestr(ts, tb) av_ts_make_time_string(TS2TIME_BUF, ts, tb)
/*--------------------------------------------打印出packet的时间戳信息--------------------------------------*/
static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, const char *tag)
{
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s stream_index:%d\n",
tag,
av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base),
av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base),
av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base),
pkt->stream_index);
}
/*--------------------------------------------主函数--------------------------------------*/
int main(int argc, char *argv[]) {
int ret;
int len; //写入的值
int videoIndex = 0;
char *filePath = "src/data/input.mp4"; //输入文件名
char *outputPath = "src/data/output.flv"; //输出文件名
AVFormatContext *inputFmtCtx = NULL, *outputFmtCtx=NULL;
AVPacket pkt ;
FILE *fdOutput=NULL;
int stream_index = 0;
av_register_all();
av_log_set_level(AV_LOG_DEBUG); //设置全局打印等级
/*--打开文件--*/
ret = avformat_open_input(&inputFmtCtx, filePath, NULL, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Open file failed: %s\n", av_err2str(ret));
goto end;
}
/*--打印文件信息--*/
av_dump_format(inputFmtCtx, 0, filePath, 0);
av_log(NULL, AV_LOG_INFO, "dump format success\n" );
/*--创建输出上下文--*/
ret =avformat_alloc_output_context2(&outputFmtCtx, NULL, NULL, outputPath);
if (!outputFmtCtx) {
av_log(NULL, AV_LOG_ERROR, "avformat alloc output contextfailed: %s\n", av_err2str(AVERROR_UNKNOWN));
goto end;
}
uint32_t stream_mapping_size = inputFmtCtx->nb_streams; //获取流的大小
int *stream_mapping = NULL;
stream_mapping=(int *)av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping)); //分配内存
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}
AVOutputFormat *outputFmt= outputFmtCtx->oformat; //获取输出格式
for (int i = 0; i < stream_mapping_size; i++) {
AVStream *outStream;
AVStream *inStream = inputFmtCtx->streams[i]; //获取输入流
AVCodecParameters *inCodecPar = inStream->codecpar; //获取输入流参数
/*--判断解码类型是 视频 音频 字幕其中一个就继续本次循环--*/
if (inCodecPar->codec_type != AVMEDIA_TYPE_AUDIO && inCodecPar->codec_type != AVMEDIA_TYPE_VIDEO && inCodecPar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1; //如果这个流不需要,就设为-1
continue;
}
stream_mapping[i] = stream_index++;
/*--创建新流--*/
outStream = avformat_new_stream(outputFmtCtx, NULL);
if (!outStream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
/*--拷贝参数--*/
ret = avcodec_parameters_copy(outStream->codecpar, inCodecPar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec Parameters\n");
goto end;
}
outStream->codecpar->codec_tag = 0;
}
av_dump_format(outputFmtCtx, 0, outputPath, 1); //把拷贝进来的参数打印出来
if (!(outputFmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&outputFmtCtx->pb, outputPath, AVIO_FLAG_WRITE); //打开设备
if (ret < 0) {
fprintf(stderr, "Failed to open output file\n");
goto end;
}
}
/*--写入头--*/
ret = avformat_write_header(outputFmtCtx, NULL);
if (ret < 0) {
fprintf(stderr, "Failed to write header to output file\n");
goto end;
}
av_init_packet(&pkt);
while (1)
{
AVStream *in_stream, *out_stream;
ret = av_read_frame(inputFmtCtx, &pkt); //读取一包数据
if (ret < 0)
{
av_log(NULL, AV_LOG_DEBUG, "fail to read frame,err is %s\n",av_err2str(ret));
break;
}
in_stream = inputFmtCtx->streams[pkt.stream_index]; //获取流
if (pkt.stream_index >= stream_mapping_size || stream_mapping[pkt.stream_index] < 0) //如果不是音视频流或者index大小不对,释放
{
av_packet_unref(&pkt);
continue;
}
pkt.stream_index = stream_mapping[pkt.stream_index];
out_stream = outputFmtCtx->streams[pkt.stream_index]; //获取输出流
/*--同步dts和pts--*/
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,(AVRounding)( AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts= av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
ret = av_interleaved_write_frame(outputFmtCtx, &pkt); //数据写入
if (ret < 0)
{
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&pkt);
}
av_write_trailer(outputFmtCtx); //写入尾部
av_log(NULL, AV_LOG_DEBUG, "write trailer success\n");
end:
avformat_close_input(&inputFmtCtx);
if (outputFmtCtx && !(outputFmt->flags & AVFMT_NOFILE))
avio_closep(&outputFmtCtx->pb);
avformat_free_context(outputFmtCtx);
av_freep(&stream_mapping);
if (ret < 0 && ret != AVERROR_EOF)
{
fprintf(stderr, "Error occurred:%s\n", av_err2str(ret));
return 1;
}
fprintf(stdout, "press enter to quit");
getchar();
return 0;
}
MP4截取
函数
av_seek_frame: 寻找到一帧
#include <stdio.h>
#include "stdafx.h"
#include <string.h>
extern "C"
{
#include "libavformat\avformat.h"
#include "libavutil\log.h"
#include "libavutil\timestamp.h"
}
/*--------------------------------------------宏--------------------------------------*/
/*--错误值转错误自符--*/
char ERR_BUF[AV_ERROR_MAX_STRING_SIZE] = { 0 };
#define av_err2str(errnum) av_make_error_string(ERR_BUF,AV_ERROR_MAX_STRING_SIZE,errnum)
char TSBUF[AV_TS_MAX_STRING_SIZE] = { 0 };
#define av_ts2str(ts) av_ts_make_string(TSBUF,ts)
char TS2TIME_BUF[AV_TS_MAX_STRING_SIZE] = { 0 };
#define av_ts2timestr(ts, tb) av_ts_make_time_string(TS2TIME_BUF, ts, tb)
int cut_video(double from_seconds, double end_seconds, const char * in_filename, const char *out_filename) {
AVOutputFormat *outputFmt = NULL;
AVFormatContext *inputFmtCtx = NULL, *outputFmtCtx = NULL;
AVPacket pkt;
int ret, i;
av_register_all();
if ((ret = avformat_open_input(&inputFmtCtx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Couldnt open input file %s\n", in_filename);
goto end;
}
if ((ret = avformat_find_stream_info(inputFmtCtx, NULL)) < 0) {
fprintf(stderr, "Couldnt find stream info \n");
goto end;
}
av_dump_format(inputFmtCtx, 0, in_filename, 0); //打印信息
avformat_alloc_output_context2(&outputFmtCtx, NULL, NULL, out_filename); //打开输出文件
if (!outputFmtCtx) {
fprintf(stderr, "Couldnt alloc output context\n");
goto end;
}
outputFmt = outputFmtCtx->oformat; //获取输出格式
/*--循环 利用输入流创建新流 ---*/
for (i = 0; i < inputFmtCtx->nb_streams; i++) {
AVStream *in_stream = inputFmtCtx->streams[i];//获取输入流
AVStream *out_stream = avformat_new_stream(outputFmtCtx, in_stream->codec->codec); //获取一个新流
if (!out_stream) {
fprintf(stderr, "failed to copy context from input stream codec ctx\n");
goto end;
}
/*--拷贝codecCtx--*/
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
fprintf(stderr, "Could not open output file %s\n", out_filename);
goto end;
}
out_stream->codec->codec_tag = 0;
if (outputFmtCtx->oformat->flags & AVFMT_GLOBALHEADER) { //如果没有全局头
out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
}
av_dump_format(outputFmtCtx, 0, out_filename, 1); //打印输出上下文信息
if (!(outputFmtCtx->flags & AVFMT_NOFILE)) { //如果没有文件,就创建
ret = avio_open(&outputFmtCtx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file %s\n", out_filename);
goto end;
}
}
ret = avformat_write_header(outputFmtCtx, NULL); //写头
if (ret < 0) {
fprintf(stderr, "Could not write header %s\n", av_err2str(ret));
goto end;
}
/*--寻找帧--*/
ret = av_seek_frame(inputFmtCtx, -1, from_seconds*AV_TIME_BASE, AVSEEK_FLAG_ANY);
if (ret < 0) {
fprintf(stderr, "Could seek frame %s\n", av_err2str(ret));
goto end;
}
/*--dts和pts堆的分配--*/
int64_t *dts_start_from = (int64_t *)malloc(sizeof(int64_t)*inputFmtCtx->nb_streams);
memset(dts_start_from, 0, sizeof(int64_t)*inputFmtCtx->nb_streams);
int64_t *pts_start_from = (int64_t *)malloc(sizeof(int64_t)*inputFmtCtx->nb_streams);
memset(pts_start_from, 0, sizeof(int64_t)*inputFmtCtx->nb_streams);
while (1) {
AVStream *in_stream = NULL, *out_stream = NULL;
ret = av_read_frame(inputFmtCtx, &pkt); //读取一包数据
if (ret < 0) //读取错误或者读q取完毕就退出
break;
in_stream = inputFmtCtx->streams[pkt.stream_index];
out_stream = outputFmtCtx->streams[pkt.stream_index];
if (av_q2d(in_stream->time_base)*pkt.pts > end_seconds) { //如果显示时间戳大于结束时间,就退出循环
av_free_packet(&pkt);
break;
}
if (dts_start_from[pkt.stream_index] == 0) { //从包里获取时间戳
dts_start_from[pkt.stream_index] == pkt.dts;
fprintf(stdout, "pts start from :%s\n", av_ts2str(dts_start_from[pkt.stream_index]));
}
if (pts_start_from[pkt.stream_index] == 0) { //从包里获取时间戳
pts_start_from[pkt.stream_index] == pkt.pts;
fprintf(stdout, "pts start from :%s\n", av_ts2str(pts_start_from[pkt.stream_index]));
}
/*--对时间戳进行转换--*/
pkt.pts = av_rescale_q_rnd(pkt.pts - pts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base,AVRounding (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
pkt.dts = av_rescale_q_rnd(pkt.dts - dts_start_from[pkt.stream_index], in_stream->time_base, out_stream->time_base,AVRounding( AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
if (pkt.pts < 0)
pkt.pts = 0;
if (pkt.dts < 0)
pkt.dts = 0;
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
/*--把包数据写入output--*/
ret = av_interleaved_write_frame(outputFmtCtx, &pkt);
if (ret < 0) {
fprintf(stderr, "Could write frame %s\n", av_err2str(ret));
break;
}
/*--资源释放--*/
av_free_packet(&pkt);
}
free(dts_start_from);
free(pts_start_from);
/*--写尾--*/
av_write_trailer(outputFmtCtx);
end:
avformat_close_input(&inputFmtCtx);
if (outputFmtCtx && !(outputFmt->flags & AVFMT_NOFILE)) {
avio_closep(&(outputFmtCtx->pb)); //释放avio
}
avformat_free_context(outputFmtCtx);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
int main(int argc, char *argv[]) {
char inputName[50] = { 0 };
char outputName[50] = { 0 };
char starttime_str[10] = { 0 };
char endtime_str[10] = { 0 };
printf("Please input a input filename");
gets_s(inputName);
printf("Please input a output filename");
gets_s(outputName);
printf("Please input starttime");
gets_s(starttime_str);
printf("Please input endtime");
gets_s(endtime_str);
double starttime = atoi(starttime_str);
double endtime = atoi(endtime_str);
/*--调用函数 --*/
cut_video(starttime, endtime,inputName,outputName) ;
getchar();
return 0;
}