FFmpeg小白學(xué)習(xí)記錄(四)音頻流編解碼流程

音頻解碼與編碼流程

解碼流程

音頻編解碼流程與視頻編解碼流程一致广匙,我們可以對 mp4 文件的音頻流進行解碼,并將解碼后的音頻數(shù)據(jù)保存到 PCM 文件中,后續(xù)我們可以通過讀取 PCM 文件中的數(shù)據(jù)實現(xiàn)音頻流的編碼操作

FFmpeg音頻解碼流程
extern"C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
#include <iostream>
using namespace std;

//將解碼后的數(shù)據(jù)寫入輸出文件中
void savePCMDecode(AVCodecContext* codecCtx, AVPacket* pkt, AVFrame* frame, FILE* file) {
    //發(fā)送包數(shù)據(jù)去進行解析獲得幀數(shù)據(jù)
    if (avcodec_send_packet(codecCtx, pkt) >= 0) {
        //接收的幀數(shù)據(jù)
        while (avcodec_receive_frame(codecCtx, frame) >= 0) {
            /*
              Planar(平面)拂到,其數(shù)據(jù)格式排列方式為 (特別記住惧笛,該處是以點nb_samples采樣點來交錯积蔚,不是以字節(jié)交錯):
              [LLLLLLRRRRRR][LLLLLLRRRRRR][LLLLLLRRRRRR]...
              每個LLLLLLRRRRRR為一個音頻幀
              而非Planar的數(shù)據(jù)格式(即交錯排列)排列方式為:
              LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每個LR為一個音頻樣本)
            */
            if (av_sample_fmt_is_planar(codecCtx->sample_fmt)) {
                //pcm播放時是LRLRLR格式莺治,所以要交錯保存數(shù)據(jù)
                int numBytes = av_get_bytes_per_sample(codecCtx->sample_fmt);
                for (int i = 0; i < frame->nb_samples; i++) {
                    for (int ch = 0; ch < codecCtx->channels; ch++) {
                        fwrite(frame->data[ch] + numBytes * i, 1, numBytes, file);
                    }
                }
            }else {
                fwrite(frame->data[0], 1, frame->linesize[0], file);
            }
        }
    }
}

void savePCM() {
    //聲明所需的變量名
    AVFormatContext* fmtCtx = NULL;
    AVCodec* codec = NULL;
    AVCodecContext* codecCtx = NULL;
    AVPacket* pkt = av_packet_alloc();
    AVFrame* frame = NULL;

    //輸入與輸出文件
    const char* inFile = "target.mp4";
    const char* outFile = "result.pcm";
    FILE* file = NULL;
    int ret;

    do {
        //打開輸入文件
        fopen_s(&file, outFile, "w+b");
        if (file == NULL) {
            cout << "not open file" << endl;
            break;
        }

        //----------------- 創(chuàng)建AVFormatContext結(jié)構(gòu)體 -------------------
        ret = avformat_open_input(&fmtCtx, inFile, NULL, NULL);
        if (ret < 0) {
            cout << "not open input" << endl;
            break;
        }
        
        //----------------- 獲取多媒體文件信息 -------------------
        ret = avformat_find_stream_info(fmtCtx, NULL);
        if (ret < 0) {
            cout << "not open stream info" << endl;
            break;
        }

        av_dump_format(fmtCtx, 0, inFile, 0);

        //----------------- 查找解碼器 -------------------
        //通過查找多媒體文件中包含的流信息捂寿,找到音頻類型的流,并返回該索引值
        //還會根據(jù)流類型查找對應(yīng)的解碼器
        int audioIndex = av_find_best_stream(fmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, &codec, 0);

        if (audioIndex < 0) {
            cout << "not find audio stream" << endl;
            break;
        }else if (codec == NULL) {
            cout << "not find codec" << endl;
            break;
        }
        
        AVCodecParameters* param = fmtCtx->streams[audioIndex]->codecpar;
        
        //創(chuàng)建解碼器上下文
        codecCtx = avcodec_alloc_context3(codec);
        if (codecCtx == NULL) {
            cout << "not alloc codec context" << endl;
            break;
        }

        //傳遞相關(guān)參數(shù)
        ret = avcodec_parameters_to_context(codecCtx, param);
        if (ret < 0) {
            cout << "parameters to context fail" << endl;
            break;
        }

        codecCtx->time_base = fmtCtx->streams[audioIndex]->time_base;
        
        //----------------- 打開解碼器 -------------------
        ret = avcodec_open2(codecCtx, codec, NULL);
        if (ret < 0) {
            cout << "not open codec" << endl;
            break;
        }
        
        //打印音頻流的相關(guān)信息魁瞪,在后續(xù)的編碼中會使用
        cout << "sample_rate:" << codecCtx->sample_rate << endl;        //音頻采樣頻率
        cout << "nb_sample:" << codecCtx->frame_size<< endl;            //音頻采樣數(shù)
        cout << "channel:" << codecCtx->channels<< endl;                //音頻聲道數(shù)
        cout << "channel_layout:" << codecCtx->channel_layout<< endl;   //音頻聲道格式
        cout << "format:" << codecCtx->sample_fmt << endl;              //音頻采樣格式

        frame = av_frame_alloc();
        while (av_read_frame(fmtCtx, pkt) >= 0) {
            //是否對應(yīng)音頻流的幀
            if (pkt->stream_index == audioIndex) {
                //執(zhí)行解碼操作
                savePCMDecode(codecCtx, pkt, frame, file);
            }
            //解引用
            av_packet_unref(pkt);
        }
        //刷新解碼器中的緩存
        savePCMDecode(codecCtx, NULL, frame, file);
    } while (0);
    
    //----------------- 釋放所有指針 ------------------- 
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_close(codecCtx);
    avcodec_free_context(&codecCtx);
    avformat_free_context(fmtCtx);

    fclose(file);
}
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'target.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.48.100
  Duration: 00:03:10.36, start: 0.000000, bitrate: 773 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1280x720, 442 kb/s, 25 fps, 25 tbr, 90k tbn, 50 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 325 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
sample_rate:48000
nb_sample:1024
channel:2
channel_layout:3
format:8

可以看到音頻流的解碼方式與流程與視頻流的解碼方式與流程一致穆律,只是在視頻流中我們將視頻幀讀取為圖片進行顯示,而音頻流中我們將音頻幀讀取到 PCM 文件中导俘,可以通過 SDL或 ffplay 對 PCM 文件進行播放

請記住打印的參數(shù)峦耘,因為PCM文件中只存放了音頻數(shù)據(jù),沒有存放音頻數(shù)據(jù)相關(guān)的參數(shù)趟畏,這些參數(shù)需要我們在編碼時給AVCodecContext進行設(shè)置

根據(jù)打印信息可以知道target.mp4音頻流格式為:

采樣率:48kHz 采樣數(shù):1024 雙聲道 立體聲(3->AV_CH_LAYOUT_STEREO) 采樣格式:32位-float planar格式(8->AV_SAMPLE_FMT_FLTP)

由此可以得出PCM文件大泄逼纭:48000 * 2 * 32 / 8 * 190.36 = 73,098,240滩租,實際PCM文件為 73,097,216赋秀,相差1024應(yīng)該是 mp4 中有一幀沒有音頻數(shù)據(jù)

注意:我們保存PCM文件時已經(jīng)將 Planar 格式轉(zhuǎn)換為 packet 格式,所以PCM中音頻格式為 AV_SAMPLE_FMT_FLT

代碼分析

av_find_best_stream

av_find_best_stream根據(jù)輸入的參數(shù)查找對應(yīng)流的索引并查找對應(yīng)解碼器律想,其實和之前使用循環(huán)查找視頻流/音頻流猎莲,然后根據(jù)ID查找解碼器實現(xiàn)一樣,不過使用該函數(shù)可以減少代碼量

int av_find_best_stream(AVFormatContext *ic,
                        enum AVMediaType type,
                        int wanted_stream_nb,
                        int related_stream,
                        AVCodec **decoder_ret,
                        int flags);

參數(shù):

  • AVFormatContext *ic:AVFormatContext結(jié)構(gòu)體技即,提供streams著洼、nb_streams參數(shù)
  • enum AVMediaType type:要查找的流類型,音頻流而叼、視頻流等
  • int wanted_stream_nb:用戶指定流的索引身笤,判斷該索引的流是否符合其余參數(shù)指定的條件,該參數(shù)為 -1 時函數(shù)自動選擇
  • int related_stream:相關(guān)的流的索引葵陵,查看源碼應(yīng)該是查找同屬一個AVProgram中的流液荸,如果不需要該參數(shù)則填-1
  • AVCodec **decoder_ret:會返回對應(yīng)流的解碼器,如果不需要則填NULL
  • int flags:保留字段脱篙,目前無用

return:

成功則返回流對應(yīng)的索引值娇钱,失敗則會返回一個負數(shù):

  • AVERROR_STREAM_NOT_FOUND:沒有找到請求類型的流
  • AVERROR_DECODER_NOT_FOUND:找到對應(yīng)流伤柄,但是沒有找到對應(yīng)的解碼器

編碼流程

音頻的編碼流程與視頻的編碼流程一致,只是音頻幀格式轉(zhuǎn)換和在AVCodecContext中需要填寫的參數(shù)有所變化

void doEncode(AVFormatContext* fmtCtx, AVCodecContext* cCtx, AVPacket* packet, AVFrame* srcFrame){
    if (fmtCtx == NULL || cCtx == NULL || packet == NULL) {
        return;
    }

    int ret = 0;
    // 開始編碼;對于音頻編碼來說文搂,可以不設(shè)置pts的值(但是會出現(xiàn)警告)
    // 如果frame 為NULL适刀,則代表將編碼緩沖區(qū)中的所有剩余數(shù)據(jù)全部編碼完
    ret = avcodec_send_frame(cCtx, srcFrame);
    /*
    * 在編碼時可能會遇到下面的錯誤提示,雖然不會影響結(jié)果煤蹭,但也不好看
    * Application provided invalid, non monotonically increasing dts to muxer in stream 0:XXX
    * 上述的錯誤為packet的dts沒有線性增長笔喉,而pts在Frame中設(shè)置過會給packet的dts和pts賦值且為線性增長,錯誤出現(xiàn)原因不明
    * 猜測為格式轉(zhuǎn)換緩存中的packet記錄了最開始轉(zhuǎn)換的Frame中的pts導(dǎo)致的問題
    * 解決方案:使用 prePts 記錄上一個packet的pts硝皂,當(dāng)packet小于prePts時然遏,將packet的pts、dts賦值為prePts+1
    */
    static int prePts = 0;
    while (ret >= 0) {
        ret = avcodec_receive_packet(cCtx, packet);
        // EAGAIN 有可能是編碼器需要先緩沖一部分數(shù)據(jù)吧彪,并不是真正的編碼錯誤
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { 
            //cout<<"encode error "<<ret<<endl;
            return;
        }else if (ret < 0) {
            // 產(chǎn)生了真正的編碼錯誤
            return;
        }
        packet->pos = -1;
        av_packet_rescale_ts(packet,cCtx->time_base,fmtCtx->streams[0]->time_base);
        if (packet->pts <= prePts) {
            packet->pts = prePts + 1;
            packet->dts = prePts + 1;
        }
        prePts = packet->pts;
        
        cout << "packet size" << packet->size << " dts:" << packet->dts << " pts:" << packet->pts << " duration:" << packet->duration << endl;
        
        //該方法等同于下面注釋的代碼
        av_interleaved_write_frame(fmtCtx, packet);

        /* 每次編碼avcodec_receive_packet都會重新為packet分配內(nèi)存待侵,所以這里用完之后要主動釋放
        av_write_frame(fmtCtx, packet);
        av_packet_unref(packet);
        */
    }
}

/**
 *  判斷采樣格式對于指定的編碼器是否支持,如果支持則返回該采樣格式姨裸;否則返回編碼器支持的枚舉值最大的采樣格式
 */
static enum AVSampleFormat select_sample_fmt(const AVCodec* codec, enum AVSampleFormat sample_fmt){
    const enum AVSampleFormat* p = codec->sample_fmts;
    enum AVSampleFormat rfmt = AV_SAMPLE_FMT_NONE;
    while (*p != AV_SAMPLE_FMT_NONE) {
        if (*p == sample_fmt) {
            return sample_fmt;
        }
        if (rfmt == AV_SAMPLE_FMT_NONE) {
            rfmt = *p;
        }
        p++;
    }

    return rfmt;
}

/**
 *  返回指定編碼器接近盡量接近44100的采樣率
 */
static int select_sample_rate(const AVCodec* codec, int default_sample_rate){
    const int* p = 0;
    int best_samplerate = 0;
    if (!codec->supported_samplerates) {
        return 44100;
    }

    p = codec->supported_samplerates;
    while (*p) {
        if (*p == defalt_sample_rate) {
            return *p;
        }

        if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) {
            best_samplerate = *p;
        }

        p++;
    }

    return best_samplerate;
}

static int select_bit_rate(AVCodec* codec){
    // 對于不同的編碼器最優(yōu)碼率不一樣秧倾,單位bit/s;對于mp3來說,192kbps可以獲得較好的音質(zhì)效果傀缩。
    int bit_rate = 64000;
    AVCodecID id = codec->id;
    if (id == AV_CODEC_ID_MP3) {
        bit_rate = 192000;
    }else if (id == AV_CODEC_ID_AC3) {
        bit_rate = 192000;
    }

    return bit_rate;
}

void pcm2mp3() {
    //聲明所需的變量名
    AVFormatContext* fmtCtx = NULL;
    AVCodec* codec = NULL;
    AVCodecContext* codecCtx = NULL;
    AVPacket* pkt = av_packet_alloc();
    AVFrame* srcFrame = NULL,* dstFrame=NULL;
    int ret;

    pkt->data = NULL;
    pkt->size = 0;

    const char* inFile = "result.pcm";
    const char* outFile = "result.mp3";

    //PCM與輸出的MP3的音頻格式相關(guān)參數(shù)
    //音頻采樣頻率
    int  src_sample_rate = 48000, dst_sample_rate = 44100;
    //音頻采樣數(shù)
    int  src_nb_samples = 1024;
    //音頻聲道數(shù)
    int  src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
    //音頻聲道格式
    uint64_t src_ch_layout = AV_CH_LAYOUT_STEREO, dst_ch_layout = AV_CH_LAYOUT_STEREO;
    //音頻采樣格式
    enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT;
    enum AVSampleFormat dst_sample_fmt = AV_SAMPLE_FMT_FLT;

    do {
        //----------------- 打開輸出文件 -------------------   
        ret = avformat_alloc_output_context2(&fmtCtx, NULL, NULL, outFile);
        if (ret) {
            cout << "Cannot alloc output file context" << endl;
            break;
        }

        ret = avio_open(&fmtCtx->pb, outFile, AVIO_FLAG_READ_WRITE);
        if (ret) {
            cout << "Cannot open output file" << endl;
            break;
        }

        //----------------- 查找編碼器 -------------------   
        codec = avcodec_find_encoder(fmtCtx->oformat->audio_codec);
        if (codec == NULL) {
            cout << "Cannot find any endcoder" << endl;
            break;
        }
        //----------------- 申請編碼器上下文結(jié)構(gòu)體 -------------------  
        codecCtx = avcodec_alloc_context3(codec);
        if (codecCtx == NULL) {
            cout << "Cannot alloc context" << endl;
            break;
        }
        
        //設(shè)置相關(guān)參數(shù)
        // 對于不同的編碼器最優(yōu)碼率不一樣那先,單位bit/s
        codecCtx->bit_rate = select_bit_rate(codec);
        // 采樣率
        codecCtx->sample_rate = select_sample_rate(codec,dst_sample_rate);
        // 采樣格式
        codecCtx->sample_fmt = select_sample_fmt(codec, dst_sample_fmt);
        // 聲道格式
        codecCtx->channel_layout = dst_ch_layout;
        // 聲道數(shù)
        codecCtx->channels = av_get_channel_layout_nb_channels(codecCtx->channel_layout);
        
        //----------------- 創(chuàng)建音頻流 -------------------  
        AVStream* vStream = avformat_new_stream(fmtCtx, NULL);
            
        AVCodecParameters* param = vStream->codecpar;
        
        //----------------- 打開解碼器 -------------------  
        ret = avcodec_open2(codecCtx, codec, NULL);
        if (ret < 0) {
            cout << "avcodec_open2 fail" << endl;
            break;
        }

        //打開PCM文件
        FILE* file = NULL;
        fopen_s(&file,inFile, "rb");
        if (file == NULL) {
            cout<<("fopen fail")<<endl;
            break;
        }

        //是否進行音頻數(shù)據(jù)格式轉(zhuǎn)換
        bool needConvert = false;
        //申請Packet、Frame
        pkt = av_packet_alloc();
        srcFrame = av_frame_alloc();
        dstFrame = av_frame_alloc();

        //設(shè)置音頻幀參數(shù)
        srcFrame->nb_samples = src_nb_samples;
        dstFrame->nb_samples = codecCtx->frame_size;

        srcFrame->format = src_sample_fmt;
        dstFrame->format = codecCtx->sample_fmt;

        srcFrame->channel_layout = src_ch_layout;
        dstFrame->channel_layout = codecCtx->channel_layout;

        srcFrame->sample_rate = src_sample_rate;
        dstFrame->sample_rate = codecCtx->sample_rate;

        // 分配srcFrame對應(yīng)的內(nèi)存塊
        ret = av_frame_get_buffer(srcFrame, 0);
        if (ret < 0) {
            cout << ("av_frame_get_buffer fail %d", ret) << endl;;
            break;
        }
        // 使得srcFrame可寫
        av_frame_make_writable(srcFrame);

        // 判斷是否需要格式轉(zhuǎn)換
        if (codecCtx->sample_fmt != srcFrame->format) {
            needConvert = true;
        }else if (codecCtx->channel_layout != srcFrame->channel_layout) {
            needConvert = true;
        }else if (codecCtx->sample_rate != srcFrame->sample_rate) {
            needConvert = true;
        }

        //與SwsContext類似的結(jié)構(gòu)體赡艰,用于記錄變換所需的參數(shù)
        SwrContext* swrCtx = NULL;
        if (needConvert) {
            // 申請進行對應(yīng)轉(zhuǎn)換所需的SwrContext
            swrCtx = swr_alloc_set_opts(NULL,
                codecCtx->channel_layout, codecCtx->sample_fmt, codecCtx->sample_rate,
                srcFrame->channel_layout, (enum AVSampleFormat)srcFrame->format, srcFrame->sample_rate, 
                0, NULL);
            
            /*
            * 如果使用swr_convert_frame進行格式轉(zhuǎn)換售淡,則swr_init可以不用寫
            * 如果使用swr_convert進行格式轉(zhuǎn)換,則需要使用swr_init函數(shù)進行初始化
            */
            swr_init(swrCtx);

            if (swrCtx == NULL) {
                cout << ("swr_alloc_set_opts() fail")<<endl;
                return;
            }

            // 分配dstFrame對應(yīng)的內(nèi)存塊
            ret = av_frame_get_buffer(dstFrame, 0);
            if (ret < 0) {
                cout << ("av_frame_get_buffer fail %d", ret) << endl;
                return;
            }
            // 使得dstFrame可寫
            ret = av_frame_make_writable(dstFrame);
            if (ret < 0) {
                cout << ("av_frame_make_writable %d", ret) << endl;
                return;
            }
        }

        // 將codecCtx設(shè)置的參數(shù)傳給param慷垮,用于寫入頭文件信息
        avcodec_parameters_from_context(param, codecCtx);

        ret = avformat_write_header(fmtCtx, NULL);
        if (ret < 0) {
            cout << ("avformat_write_header %d", ret) << endl;;
            break;
        }

        av_dump_format(fmtCtx, 0, NULL, 1);

        int ptsIndex = 0;
        // 獲取一幀所需的緩沖區(qū)大小
        int require_size = av_samples_get_buffer_size(NULL, src_nb_channels, src_nb_samples, src_sample_fmt, 0);

        // 讀取PCM文件中的音頻數(shù)據(jù)
        while (fread(srcFrame->data[0], 1, require_size, file) > 0) {
            pkt->stream_index = vStream->index;
            if (needConvert) {
                // 進行格式轉(zhuǎn)換
                ret = swr_convert_frame(swrCtx, dstFrame, srcFrame);
                if (ret < 0) {
                    cout << "swr_convert_frame fail "<< ret << endl;
                    continue;
                }
                dstFrame->pts = ptsIndex;
                //編碼
                doEncode(fmtCtx, codecCtx, pkt, dstFrame);
            }else {
                srcFrame->pts = ptsIndex;
                doEncode(fmtCtx, codecCtx, pkt, srcFrame);
            }
            ptsIndex++;
        }

        if (needConvert) {
            //刷新緩存,格式不同揖闸,幀對應(yīng)的數(shù)據(jù)大小也不同
            while (swr_convert_frame(swrCtx, dstFrame, NULL) == 0) {
                if (dstFrame->nb_samples > 0) {
                    cout << "清除剩余的 "<< dstFrame->nb_samples << endl;;
                    dstFrame->pts = ptsIndex++;
                    doEncode1(fmtCtx, codecCtx, pkt, dstFrame);
                }else {
                    break;
                }
            }
        }
        //刷新緩存
        doEncode(fmtCtx, codecCtx, pkt, NULL);

        if (swrCtx) swr_free(&swrCtx);
        // 寫入收尾信息,必須要與料身,否則文件無法播放
        av_write_trailer(fmtCtx);
        fclose(file);

    } while (0);

    if (fmtCtx) {
        avformat_free_context(fmtCtx);
        avio_closep(&fmtCtx->pb);
    }
    if (codecCtx) avcodec_free_context(&codecCtx);
    if (srcFrame) av_frame_free(&srcFrame);
    if (dstFrame) av_frame_free(&dstFrame);
    av_packet_free(&pkt);
}
代碼分析

SwrContext結(jié)構(gòu)體與SwsContext結(jié)構(gòu)體類似汤纸,都是用于記錄變換所需的參數(shù)

// 其中截取出部分較為重要的數(shù)據(jù) 
struct SwrContext {
    enum AVSampleFormat  in_sample_fmt;             //輸入的采樣格式
    enum AVSampleFormat out_sample_fmt;             //輸出的采樣格式
    int64_t  in_ch_layout;                          //輸入的聲道格式
    int64_t out_ch_layout;                          //輸出的聲道格式
    int      in_sample_rate;                        //輸入的采樣頻率
    int     out_sample_rate;                        //輸出的采樣頻率
    int flags;                                      //其他標志,如SWR_FLAG_RESAMPLE
    const int *channel_map;                         //聲道m(xù)ap
    int used_ch_count;                              //已經(jīng)使用的聲道數(shù)

    int user_in_ch_count;                           //用戶設(shè)置的輸入的聲道數(shù)
    int user_out_ch_count;                          //用戶設(shè)置的輸出的聲道數(shù)
    int64_t user_in_ch_layout;                      //用戶設(shè)置的輸入的聲道格式
    int64_t user_out_ch_layout;                     //用戶設(shè)置的輸出的聲道格式
    ...
};

av_frame_get_buffer為音頻或視頻數(shù)據(jù)分配新的緩沖區(qū)芹血,在調(diào)用該函數(shù)之前贮泞,需為AVFrame設(shè)置以下字段

  • 視頻Frame

    • format(視頻為AVPixelFormat

    • width 、height

  • 音頻Frame

    • format(音頻為AVSampleFormat
    • nb_samples 幔烛、 channel_layout
int av_frame_get_buffer(AVFrame *frame, int align);

參數(shù):

  • AVFrame *frame:用于存儲新緩沖區(qū)的幀
  • int align:緩沖區(qū)中數(shù)據(jù)大小對齊方式啃擦,如果填0,則會根據(jù)cpu的架構(gòu)自動選擇對齊位數(shù)饿悬,建議填0 (有特殊需求當(dāng)我沒說)

return:

返回0表示成功令蛉,失敗會返回一個負數(shù)

可以用該函數(shù)替換視頻編碼中計算緩存大小、申請緩存乡恕、av_image_fill_arrays一系列的代碼言询,其內(nèi)部的實現(xiàn)原理差不多


swr_alloc_set_opts

swr_alloc_set_opts函數(shù)會根據(jù)需要創(chuàng)建SwrContext俯萎,設(shè)置參數(shù)。該函數(shù)不需要使用swr_alloc()來分配运杭,當(dāng)struct SwrContext *s為 NULL 時函數(shù)體內(nèi)部會調(diào)用swr_alloc()方法

該函數(shù)實際上是一系列av_opt_set_int的封裝

struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
                                      int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate,
                                      int64_t  in_ch_layout, enum AVSampleFormat  in_sample_fmt, int  in_sample_rate,
                                      int log_offset, void *log_ctx){
    if(!s) s= swr_alloc();
    if(!s) return NULL;

    s->log_level_offset= log_offset;
    s->log_ctx= log_ctx;

    if (av_opt_set_int(s, "ocl", out_ch_layout,   0) < 0) goto fail;
    if (av_opt_set_int(s, "osf", out_sample_fmt,  0) < 0) goto fail;
    if (av_opt_set_int(s, "osr", out_sample_rate, 0) < 0) goto fail;
    if (av_opt_set_int(s, "icl", in_ch_layout,    0) < 0) goto fail;
    if (av_opt_set_int(s, "isf", in_sample_fmt,   0) < 0) goto fail;
    if (av_opt_set_int(s, "isr", in_sample_rate,  0) < 0) goto fail;
    if (av_opt_set_int(s, "ich", av_get_channel_layout_nb_channels(s-> user_in_ch_layout), 0) < 0) goto fail;
    if (av_opt_set_int(s, "och", av_get_channel_layout_nb_channels(s->user_out_ch_layout), 0) < 0) goto fail;

    av_opt_set_int(s, "uch", 0, 0);
    return s;
fail:
    av_log(s, AV_LOG_ERROR, "Failed to set option\n");
    swr_free(&s);
    return NULL;
}

參數(shù):

  • struct SwrContext *s:已經(jīng)創(chuàng)建完成的SwrContext指針夫啊。為 NULL 時,函數(shù)內(nèi)部會創(chuàng)建一個SwrContext結(jié)構(gòu)體
  • int64_t out_ch_layout:輸出的聲道格式
  • enum AVSampleFormat out_sample_fmt:輸出的采樣格式
  • int out_sample_rate:輸出的采樣頻率
  • int64_t in_ch_layout:輸入的聲道格式
  • enum AVSampleFormat in_sample_fmt:輸入的采樣格式
  • int in_sample_rate:輸入的采樣格式
  • int log_offset:日志等級
  • void *log_ctx:日志上下文辆憔,可以為NULL

return:

成功返回對應(yīng)的SwrContext指針撇眯,失敗則返回NULL

其中的"ocl"、"osf"虱咧、"osr"等字符參數(shù)可以在<libswresample/options.c>文件中可以找到定義


swr_convert_frame

swr_convert_frame函數(shù)會轉(zhuǎn)換AVFrame *input的數(shù)據(jù)并寫入AVFrame *output中熊榛,但輸入與輸出Frame都必須有channel_layout, sample_rateformat參數(shù)

如果輸出Frame沒有分配數(shù)據(jù)指針,則會根據(jù)會使用av_frame_get_buffer函數(shù)申請數(shù)據(jù)內(nèi)存

如果輸出Frame為 NULL 或獲取的數(shù)據(jù)比其所需的數(shù)據(jù)量多腕巡。這種情況下玄坦,多余的數(shù)據(jù)則會被添加到內(nèi)部FIFO緩沖區(qū)中,在下一次調(diào)用此函數(shù)或swr_convert函數(shù)時返回

轉(zhuǎn)換采樣率绘沉,可能會導(dǎo)致有數(shù)據(jù)留在內(nèi)部緩存區(qū)中煎楣。可以通過Swr_get_delay返回保留在緩存區(qū)中的數(shù)據(jù)大小车伞,要獲取剩余數(shù)據(jù)作為輸出數(shù)據(jù)择懂,可以重新調(diào)用此函數(shù)或swr_convert函數(shù),并將AVFrame *input設(shè)置為NULL

int swr_convert_frame(SwrContext *swr,
                      AVFrame *output, const AVFrame *input);

參數(shù):

  • SwrContext *swr:SwrContext結(jié)構(gòu)體指針另玖,用于提供重采樣所需的參數(shù)
  • AVFrame *output:輸出的Frame
  • const AVFrame *input:輸入的Frame

return:

返回 0 表示成功困曙,失敗時返回一個負數(shù) (AVERROR)

看函數(shù)描述中可以知道還有一個函數(shù)swr_convert可以實現(xiàn)重采樣的功能,不過該函數(shù)的使用比swr_convert_frame麻煩一些

//使用案例:
if (!swr_is_initialized(swrCtx)) {
    //不能重復(fù)初始化SwrContext谦去,可能會導(dǎo)致SwrContext中的值發(fā)生變化
    ret = swr_init(swrCtx);
    if (ret < 0) {
        cout<< "swr_init fail" <<endl;
        break;
    }
}
//const uint8_t** srcdata = (const uint8_t**)srcFrame->data;      
const uint8_t** srcdata = (const uint8_t**)srcFrame->extended_data;       //可以與上方注釋代碼進行替換
uint8_t** outdata = dstFrame->extended_data;
ret = swr_convert(swrCtx, outdata, dstFrame->nb_samples, srcdata, srcFrame->nb_samples);
if (ret < 0) {
    cout << "convert fail" << endl;
    break;
}
dstFrame->nb_samples = ret;

swr_convert

int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                const uint8_t **in , int in_count);

參數(shù):

  • struct SwrContext *s:SwrContext結(jié)構(gòu)體指針慷丽,用于提供重采樣所需的參數(shù)
  • uint8_t **out:存放輸出數(shù)據(jù)的二維數(shù)組
  • int out_count:輸出音頻采樣數(shù)
  • const uint8_t **in:存放要轉(zhuǎn)換數(shù)據(jù)的二維數(shù)組
  • int in_count:輸入音頻采樣數(shù)

return:

成功則返回轉(zhuǎn)換后Frame應(yīng)采用的音頻采樣數(shù),失敗則返回一個負數(shù)

查看源碼會發(fā)現(xiàn)swr_convert_frame內(nèi)部也是調(diào)用swr_convert進行格式轉(zhuǎn)換的哪轿,其內(nèi)部代碼邏輯與上述代碼相似盈魁,還增加了一些參數(shù)合法性判斷。比較推薦使用swr_convert_frame窃诉,可以簡化代碼和增加代碼的健壯性

參考資料

pcm編碼為aac/MP3格式ffmpeg(八)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赤套,隨后出現(xiàn)的幾起案子飘痛,更是在濱河造成了極大的恐慌,老刑警劉巖容握,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣脉,死亡現(xiàn)場離奇詭異,居然都是意外死亡剔氏,警方通過查閱死者的電腦和手機塑猖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門竹祷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人羊苟,你說我怎么就攤上這事塑陵。” “怎么了蜡励?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵令花,是天一觀的道長。 經(jīng)常有香客問我凉倚,道長兼都,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任稽寒,我火速辦了婚禮扮碧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘杏糙。我一直安慰自己芬萍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布搔啊。 她就那樣靜靜地躺著柬祠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪负芋。 梳的紋絲不亂的頭發(fā)上漫蛔,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機與錄音旧蛾,去河邊找鬼莽龟。 笑死,一個胖子當(dāng)著我的面吹牛锨天,可吹牛的內(nèi)容都是我干的毯盈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼病袄,長吁一口氣:“原來是場噩夢啊……” “哼搂赋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起益缠,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤脑奠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后幅慌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宋欺,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了齿诞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酸休。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祷杈,靈堂內(nèi)的尸體忽然破棺而出斑司,到底是詐尸還是另有隱情,我是刑警寧澤吠式,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布陡厘,位于F島的核電站,受9級特大地震影響特占,放射性物質(zhì)發(fā)生泄漏糙置。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一是目、第九天 我趴在偏房一處隱蔽的房頂上張望谤饭。 院中可真熱鬧,春花似錦懊纳、人聲如沸揉抵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冤今。三九已至,卻和暖如春茂缚,著一層夾襖步出監(jiān)牢的瞬間戏罢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工脚囊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留龟糕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓悔耘,卻偏偏與公主長得像讲岁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衬以,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容