【FFMpeg視頻開發與應用基礎】六、調用FFMpeg SDK實現視頻文件的轉封裝

《FFMpeg視頻開發與應用基礎——使用FFMpeg工具與SDK》視頻教程已經在“CSDN學院”上線,視頻中包含了從0開始逐行代碼實現FFMpeg視頻開發的過程,歡迎觀看!鏈接地址:FFMpeg視頻開發與應用基礎——使用FFMpeg工具與SDK

工程代碼地址:FFmpeg_Tutorial


有時候我們可能會面對這樣的一種需求,即我們不需要對視頻內的音頻或視頻信號進行什麼實際的操作,只是希望能把文件的封裝格式進行轉換,例如從avi轉換爲mp4格式或者flv格式等。實際上,轉封裝不需要對內部的音視頻進行解碼,只需要根據從輸入文件中獲取包含的數據流添加到輸出文件中,然後將輸入文件中的數據包按照規定格式寫入到輸出文件中去。


1、解析命令行參數

如同之前的工程一樣,我們使用命令行參數傳入輸入和輸出的文件名。爲此,我們定義瞭如下的結構體和函數來實現傳入輸入輸出文件的過程:

typedef struct _IOFiles
{
    const char *inputName;
    const char *outputName;
} IOFiles;

static bool hello(int argc, char **argv, IOFiles &io_param)
{
    printf("FFMpeg Remuxing Demo.\nCommand format: %s inputfile outputfile\n", argv[0]);
    if (argc != 3)
    {
        printf("Error: command line error, please re-check.\n");
        return false;
    }

    io_param.inputName = argv[1];
    io_param.outputName = argv[2];

    return true;
}

在main函數執行時,調用hello函數解析命令行並保存到IOFiles結構中:

int main(int argc, char **argv)
{
    IOFiles io_param;
    if (!hello(argc, argv, io_param))
    {
        return -1;
    }
    //......
}

2、所需要的結構與初始化操作

爲了實現視頻文件的轉封裝操作,我們需要以下的結構:

AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;

然後所需要的初始化操作有打開輸入視頻文件、獲取其中的流信息和獲取輸出文件的句柄:

av_register_all();

//按封裝格式打開輸入視頻文件
if ((ret = avformat_open_input(&ifmt_ctx, io_param.inputName, NULL, NULL)) < 0)
{
    printf("Error: Open input file failed.\n");
    goto end;
}

//獲取輸入視頻文件中的流信息
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
{
    printf("Error: Failed to retrieve input stream information.\n");
    goto end;
}
av_dump_format(ifmt_ctx, 0, io_param.inputName, 0);

//按照文件名獲取輸出文件的句柄
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, io_param.outputName);
if (!ofmt_ctx)
{
    printf("Error: Could not create output context.\n");
    goto end;
}
ofmt = ofmt_ctx->oformat;

3、 向輸出文件中添加Stream並打開輸出文件

在我們獲取到了輸入文件中的流信息後,保持輸入流中的codec不變,並以其爲依據添加到輸出文件中:

for (unsigned int i = 0; i < ifmt_ctx->nb_streams ; i++)
{
    AVStream *inStream = ifmt_ctx->streams[i];
    AVStream *outStream = avformat_new_stream(ofmt_ctx, inStream->codec->codec);
    if (!outStream)
    {
        printf("Error: Could not allocate output stream.\n");
        goto end;
    }

    ret = avcodec_copy_context(outStream->codec, inStream->codec);
    outStream->codec->codec_tag = 0;
    if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
    {
        outStream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
}

av_dump_format(ofmt_ctx, 0, io_param.outputName, 1);

這裏調用了函數avcodec_copy_context函數,該函數的聲明如下:

int avcodec_copy_context(AVCodecContext *dest, const AVCodecContext *src);

該函數的作用是將src表示的AVCodecContext中的內容拷貝到dest中。

隨後,調用avio_open函數打開輸出文件:

av_dump_format(ofmt_ctx, 0, io_param.outputName, 1);

if (!(ofmt->flags & AVFMT_NOFILE))
{
    ret = avio_open(&ofmt_ctx->pb, io_param.outputName, AVIO_FLAG_WRITE);
    if (ret < 0)
    {
        printf("Error: Could not open output file.\n");
        goto end;
    }
}

4、寫入文件的音視頻數據

首先向輸出文件中寫入文件頭:

ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) 
{
    printf("Error: Could not write output file header.\n");
    goto end;
}

寫入文件的視頻和音頻包數據,其實就是將音頻和視頻Packets從輸入文件中讀出來,正確設置pts和dts等時間量之後,再寫入到輸出文件中去:

while (1) 
{
    AVStream *in_stream, *out_stream;

    ret = av_read_frame(ifmt_ctx, &pkt);
    if (ret < 0)
        break;

    in_stream  = ifmt_ctx->streams[pkt.stream_index];
    out_stream = ofmt_ctx->streams[pkt.stream_index];

    /* copy packet */
    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(ofmt_ctx, &pkt);
    if (ret < 0) 
    {
        fprintf(stderr, "Error muxing packet\n");
        break;
    }
    av_free_packet(&pkt);
}

最後要做的就是寫入文件尾:

av_write_trailer(ofmt_ctx);

5、 收尾工作

寫入輸出文件完成後,需要對打開的結構進行關閉或釋放等操作。主要有關閉輸入輸出文件、釋放輸出文件的句柄等:

avformat_close_input(&ifmt_ctx);

/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
    avio_closep(&ofmt_ctx->pb);

avformat_free_context(ofmt_ctx);

if (ret < 0 && ret != AVERROR_EOF) 
{
    fprintf(stderr, "Error failed to write packet to output file.\n");
    return 1;
}
發佈了185 篇原創文章 · 獲贊 125 · 訪問量 45萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章