ffmpeg抽取音视频,格式转换,截取

下面内容由ffmpeg官网的例程源码分析所得。由于fmpeg是c语言所写,并且这里以分析和提取ffmpeg工作流程为主,就没有对代码进行解耦合。

音视频相关知识比较杂乱,单纯看代码,不是音视频的老手或者对音视频格式很熟悉,会对流中的一些操作感到困惑,如在视频头尾添加各种码,参数的设置和拷贝等,感觉在阅读代码时多参考相关文献,或者百度, 不然很难掌握。

官方例程源码链接 http://www.ffmpeg.org/doxygen/trunk/examples.html

下面的内容只单纯的进行音视频的抽取和格式转换,并没有进行编解码。

音频抽取

目的:对封装好的文件,单独提取音频

一些关键函数

  1. av_init_packet 初始化包。
  2. av_find_best_steam在多媒体里面找到最好(适合)的流,如果在传参传入 视频流类型宏 ,就会返回视频流的流编号,音频或字幕流同理。
  3. av_read_frame:获取数据包,不是帧。值得注意的是,明明read的是frame(帧),而最终读取出来的是包。这里之所以用frame而不用packet,就是因为在早期版本里面,解码前的帧和解码后的帧都是frame.所以现在就没改。
  4. 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头。具体的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字节数据,后五位表示帧类型 实际数据 。。。。
  1. start code:在处理视频信息的时候,在每一帧的视频数据前面,有个信息头。或者有个特征码,来标志这一帧开始上一帧结束。这样形成的文件才可以被播放器播放。对于sps/pps,特征码就是4个字节,0x00000001,对于非sps/pps,就是3个字节。0x0000001。
  2. SPS/PPS: 解码视频的参数,视频分辨率帧率等。一个普通视频中,只需要一个SPS/PPS即可。关键帧前面必要要有这俩。
  3. 视频编码成的包叫NALU,NAL,网络抽象层单元(Network abstraction layer units),
  4. 一个包里,如果帧很小,那就很多帧,如果帧大,那么一包里面可能就1帧。而pkt里面每一帧的开头,有四字节(32位)数据来表示帧的大小。获取到这个帧的大小,就可以得到这个帧的数据有多大。
    这个表示帧大小的4字节数据,存储方式应该是小端,高字节的8位在后,低字节在前(实际验证了确实是这样),那么要得到这个帧的大小,需要一个4次的循环,第一次取出pkg->data前八位,左移8位,再从pkg->data中得到第2个字节的内容,再左移八位。。。
  5. 帧实际数据的第一个字节为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;
}

猜你喜欢

转载自blog.csdn.net/qq_26144489/article/details/113819641