FFmpeg代碼實現抽取音頻、視頻數據

今天開始擼代碼,首先使用FFmpeg的API抽取一個MP4文件的音頻數據。數組

IDE

應該是第一次在Mac上作C/C++開發,糾結事後選擇使用CLion 開發。CLion是 JetBrains下專門用來開發C/C++的IDE,已經用習慣了Android studio和IntelliJ IDEA ,因此CLion用起來仍是很順手的。ide

在新建一個C項目後,須要把FFmpeg的庫導入才能正常運行。咱們修改項目的CMakeLists.txt文件。優化

抽取音頻AAC數據

其實咱們要作的主要就是一個文件的操做,把一個文件打開,從裏面拿出它的一部分數據,再把這部分數據放到另外一個文件中保存。ui

定義參數
#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>

//上下文
AVFormatContext *fmt_ctx = NULL;
AVFormatContext *ofmt_ctx = NULL;

//支持各類各樣的輸出文件格式,MP4,FLV,3GP等等
AVOutputFormat *output_fmt = NULL;

//輸入流
AVStream *in_stream = NULL;

//輸出流
AVStream *out_stream = NULL;

//存儲壓縮數據
AVPacket packet;

//要拷貝的流
int audio_stream_index = -1;
複製代碼

1.打開輸入文件,提取參數

//打開輸入文件,關於輸入文件的全部就保存到fmt_ctx中了
err_code = avformat_open_input(&fmt_ctx, src_fileName, NULL, NULL);

if (err_code < 0) {
    av_log(NULL, AV_LOG_ERROR, "cant open file:%s\n", av_err2str(err_code));
    return -1;
}

if(fmt_ctx->nb_streams<2){
      //流數小於2,說明這個文件音頻、視頻流這兩條都不能保證,輸入文件有錯誤 
      av_log(NULL, AV_LOG_ERROR, "輸入文件錯誤,流不足2條\n");
      exit(1);
 }

 //拿到文件中音頻流
 in_stream = fmt_ctx->streams[1];
 //參數信息
 AVCodecParameters *in_codecpar = in_stream->codecpar;

//找到最好的音頻流
audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if(audio_stream_index < 0){
        av_log(NULL, AV_LOG_DEBUG, "尋找最好音頻流失敗,請檢查輸入文件!\n");
        return AVERROR(EINVAL);
}
複製代碼

2.準備輸出文件,輸出流

// 輸出上下文
ofmt_ctx = avformat_alloc_context();

//根據目標文件名生成最適合的輸出容器
output_fmt = av_guess_format(NULL,dst_fileName,NULL);
if(!output_fmt){
    av_log(NULL, AV_LOG_DEBUG, "根據目標生成輸出容器失敗!\n");
    exit(1);
}

ofmt_ctx->oformat = output_fmt;

//新建輸出流
 out_stream = avformat_new_stream(ofmt_ctx, NULL);
 if(!out_stream){
      av_log(NULL, AV_LOG_DEBUG, "建立輸出流失敗!\n");
      exit(1);
 }
複製代碼

3. 數據拷貝

3.1 參數信息

// 將參數信息拷貝到輸出流中,咱們只是抽取音頻流,並不作音頻處理,因此這裏只是Copy
if((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0 ){
    av_strerror(err_code, errors, ERROR_STR_SIZE);
    av_log(NULL, AV_LOG_ERROR,"拷貝編碼參數失敗!, %d(%s)\n",
           err_code, errors);
}
複製代碼

3.2 初始化AVIOContext

//初始化AVIOContext,文件操做由它完成
if((err_code = avio_open(&ofmt_ctx->pb, dst_fileName, AVIO_FLAG_WRITE)) < 0) {
    av_strerror(err_code, errors, 1024);
    av_log(NULL, AV_LOG_DEBUG, "Could not open file %s, %d(%s)\n",
           dst_fileName,
           err_code,
           errors);
    exit(1);
}
複製代碼

3.3 開始拷貝

//初始化 AVPacket, 咱們從文件中讀出的數據會暫存在其中
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;

// 寫頭部信息
if (avformat_write_header(ofmt_ctx, NULL) < 0) {
    av_log(NULL, AV_LOG_DEBUG, "Error occurred when opening output file");
    exit(1);
}


//每讀出一幀數據
while(av_read_frame(fmt_ctx, &packet) >=0 ){
    if(packet.stream_index == audio_stream_index){
        //時間基計算,音頻pts和dts一致
        packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        packet.dts = packet.pts;
        packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base);
        packet.pos = -1;
        packet.stream_index = 0;
        //將包寫到輸出媒體文件
        av_interleaved_write_frame(ofmt_ctx, &packet);
        //減小引用計數,避免內存泄漏
        av_packet_unref(&packet);
    }
}

//寫尾部信息
av_write_trailer(ofmt_ctx);

//最後別忘了釋放內存
avformat_close_input(&fmt_ctx);
avio_close(ofmt_ctx->pb);
複製代碼

執行

./MyC /Users/david/Desktop/1080p.mov /Users/david/Desktop/test.aac編碼

抽取視頻數據

抽取視頻信息並保存在文件中的流程甚至代碼和上面抽取音頻基本一致。spa

//拿到文件中音頻流 或者 視頻流,全部流都在streams數組中
 in_stream = fmt_ctx->streams[1];

//找到最好的視頻流
video_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, (AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
複製代碼

基本上就是一些參數的改變,全部流程和代碼保持不變,就能夠把一個音視頻文件中的視頻數據抽取出來了,mp四、mov等格式隨便,就是這麼簡單。。。code

更新

====== 貼出完整代碼,並對代碼中的一些細節作出優化========orm

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>

#define ERROR_STR_SIZE 1024

int main(int argc, char *argv[]) {
    int err_code;
    char errors[1024];

    char *src_filename = NULL;
    char *dst_filename = NULL;

    int audio_stream_index;

    //上下文
    AVFormatContext *fmt_ctx = NULL;
    AVFormatContext *ofmt_ctx = NULL;

    //支持各類各樣的輸出文件格式,MP4,FLV,3GP等等
    AVOutputFormat *output_fmt = NULL;

    AVStream *in_stream = NULL;
    AVStream *out_stream = NULL;

    AVPacket pkt;

    av_log_set_level(AV_LOG_DEBUG);

    if (argc < 3) {
        av_log(NULL, AV_LOG_DEBUG, "argc < 3!\n");
        return -1;
    }

    src_filename = argv[1];
    dst_filename = argv[2];

    if (src_filename == NULL || dst_filename == NULL) {
        av_log(NULL, AV_LOG_DEBUG, "src or dts file is null!\n");
        return -1;
    }


    if ((err_code = avformat_open_input(&fmt_ctx, src_filename, NULL, NULL)) < 0) {
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "打開輸入文件失敗: %s, %d(%s)\n",
               src_filename,
               err_code,
               errors);
        return -1;
    }

    if ((err_code = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "failed to find stream info: %s, %d(%s)\n",
               src_filename,
               err_code,
               errors);
        return -1;
    }

    av_dump_format(fmt_ctx, 0, src_filename, 0);

    if (fmt_ctx->nb_streams < 2) {
        //流數小於2,說明這個文件音頻、視頻流這兩條都不能保證,輸入文件有錯誤
        av_log(NULL, AV_LOG_ERROR, "輸入文件錯誤,流不足2條\n");
        exit(1);
    }

    //拿到文件中音頻流
    /**只須要修改這裏AVMEDIA_TYPE_VIDEO參數**/
    audio_stream_index = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO /*AVMEDIA_TYPE_VIDEO*/, -1, -1, NULL, 0);
    if (audio_stream_index < 0) {
        av_log(NULL, AV_LOG_DEBUG, " 獲取音頻流失敗%s,%s\n",
               av_get_media_type_string(AVMEDIA_TYPE_AUDIO),
               src_filename);
        return AVERROR(EINVAL);
    }

    in_stream = fmt_ctx->streams[audio_stream_index];
    //參數信息
    AVCodecParameters *in_codecpar = in_stream->codecpar;


    // 輸出上下文
    ofmt_ctx = avformat_alloc_context();

    //根據目標文件名生成最適合的輸出容器
    output_fmt = av_guess_format(NULL, dst_filename, NULL);
    if (!output_fmt) {
        av_log(NULL, AV_LOG_DEBUG, "根據目標生成輸出容器失敗!\n");
        exit(1);
    }

    ofmt_ctx->oformat = output_fmt;

    //新建輸出流
    out_stream = avformat_new_stream(ofmt_ctx, NULL);
    if (!out_stream) {
        av_log(NULL, AV_LOG_DEBUG, "建立輸出流失敗!\n");
        exit(1);
    }

    // 將參數信息拷貝到輸出流中,咱們只是抽取音頻流,並不作音頻處理,因此這裏只是Copy
    if ((err_code = avcodec_parameters_copy(out_stream->codecpar, in_codecpar)) < 0) {
        av_strerror(err_code, errors, ERROR_STR_SIZE);
        av_log(NULL, AV_LOG_ERROR,
               "拷貝編碼參數失敗!, %d(%s)\n",
               err_code, errors);
    }

    out_stream->codecpar->codec_tag = 0;

    //初始化AVIOContext,文件操做由它完成
    if ((err_code = avio_open(&ofmt_ctx->pb, dst_filename, AVIO_FLAG_WRITE)) < 0) {
        av_strerror(err_code, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "文件打開失敗 %s, %d(%s)\n",
               dst_filename,
               err_code,
               errors);
        exit(1);
    }



    av_dump_format(ofmt_ctx, 0, dst_filename, 1);


    //初始化 AVPacket, 咱們從文件中讀出的數據會暫存在其中
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;


    // 寫頭部信息
    if (avformat_write_header(ofmt_ctx, NULL) < 0) {
        av_log(NULL, AV_LOG_DEBUG, "寫入頭部信息失敗!");
        exit(1);
    }

    //每讀出一幀數據
    while (av_read_frame(fmt_ctx, &pkt) >= 0) {
        if (pkt.stream_index == audio_stream_index) {
            pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base,
                                       (AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base,
                                       (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;
            pkt.stream_index = 0;
            //將包寫到輸出媒體文件
            av_interleaved_write_frame(ofmt_ctx, &pkt);
            //減小引用計數,避免內存泄漏
            av_packet_unref(&pkt);
        }
    }

    //寫尾部信息
    av_write_trailer(ofmt_ctx);

    //最後別忘了釋放內存
    avformat_close_input(&fmt_ctx);
    avio_close(ofmt_ctx->pb);

    return 0;
}
複製代碼

./MyC /Users/david/Desktop/1080p.mov /Users/david/Desktop/test.aaccdn

只須要修改av_find_best_stream中的參數,執行如下命令就能夠將視頻流提取,成爲單獨的視頻文件視頻

./MyC /Users/david/Desktop/1080p.mov /Users/david/Desktop/test1.mp4

./MyC /Users/david/Desktop/1080p.mov /Users/david/Desktop/test2.mov

相關文章
相關標籤/搜索