音頻重采樣ffmpeg(九)

前言

廣義的音頻重采樣包括:
1、采樣格式轉(zhuǎn)化:比如采樣格式從16位整形變?yōu)楦↑c(diǎn)型
2驱犹、采樣率的轉(zhuǎn)換:降采樣和升采樣施禾,比如44100采樣率降為2000采樣率
3、存放方式轉(zhuǎn)化:音頻數(shù)據(jù)從packet方式變?yōu)閜lanner方式屎媳。有的硬件平臺在播放聲音時(shí)需要的音頻數(shù)據(jù)是planner格式的,而有的可能又是packet格式的论巍,或者其它需求原因經(jīng)常需要進(jìn)行這種存放方式的轉(zhuǎn)化

通常意義上的音頻重采樣是指上述的第2點(diǎn)烛谊,即升采樣和降采樣,這個(gè)對音質(zhì)有一定影響

重采樣相關(guān)函數(shù)流程圖

1584929874392.jpg

重采樣ffmpeg相關(guān)函數(shù)介紹

這些重采樣函數(shù)都位于頭文件<libswresampe/swresample.h>

  • 1嘉汰、SwrContext
    實(shí)現(xiàn)重采樣的上下文丹禀,它是一個(gè)結(jié)構(gòu)體,具體定義未知
  • 2鞋怀、SwrContext *swr_alloc();
    分配上下對象
  • 3双泪、
    int av_opt_set_int(void *obj,char name,int64_t val,int search_flags);
    int av_opt_set_sample_fmt(void
    obj,char *name,enum AVSampleFormat fmt,int search_flags);
    給上下文對象設(shè)置重采樣的轉(zhuǎn)換參數(shù)
  • 4、int swr_init(struct SwrContext * swrCtx);
    設(shè)置參數(shù)之后初始化重采樣上下文
  • 5密似、struct SwrContext* swr_alloc_set_opts(struct SwrContext *swr_ctx,
    int64_t out_ch_layout,enum AVSampleFormat out_sample_fmt,int out_sample_rate,
    int64_t in_ch_layout,enum AVSampleFormat in_ch_layout,int in_sample_rate
    );
    此函數(shù)功能相當(dāng)于上面1焙矛、2、3辛友、4所有函數(shù)的功能
  • 6薄扁、int swr_convert(struct SwrContext swrCtx,uint8_t* out,int out_count,
    const uint8_t **int,int in_count)
    進(jìn)行格式轉(zhuǎn)換,返回值為實(shí)際轉(zhuǎn)換的采樣數(shù)废累,該值小于等于out_count
  • 7邓梅、int swr_convert_frame(struct SwrContext *swrCtx,AVFrame **outframe,AVFrame inframe);
    返回值為0代表轉(zhuǎn)換成功。當(dāng)進(jìn)行升采/降樣的時(shí)候邑滨,內(nèi)部緩沖中會包含部分?jǐn)?shù)據(jù)日缨,要想全部獲取這些數(shù)據(jù),inframe要傳遞NULL
  • 8掖看、void swr_free(struct SwrContext **s);
    釋放上下文

ffmpeg實(shí)現(xiàn)音頻重采樣

ffmpeg提供了實(shí)現(xiàn)廣義重采樣的方式匣距,通過libswresample庫實(shí)現(xiàn),可以通過具體實(shí)現(xiàn)代碼如下:
頭文件

#ifndef audio_resample_hpp
#define audio_resample_hpp

#include <stdio.h>
#include <string>
#include "cppcommon/CLog.h"

using namespace std;

extern "C" {
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
}

/** 廣義的音頻的重采樣包括:
 *  1哎壳、采樣格式轉(zhuǎn)化:比如采樣格式從16位整形變?yōu)楦↑c(diǎn)型
 *  2毅待、采樣率的轉(zhuǎn)換:降采樣和升采樣,比如44100采樣率降為2000采樣率
 *  3归榕、存放方式轉(zhuǎn)化:音頻數(shù)據(jù)從packet方式變?yōu)閜lanner方式尸红。有的硬件平臺在播放聲音時(shí)需要的音頻數(shù)據(jù)是
 *  planner格式的,而有的可能又是packet格式的,或者其它需求原因經(jīng)常需要進(jìn)行這種存放方式的轉(zhuǎn)化
 */
class AudioResample
{
public:
    AudioResample();
    ~AudioResample();
    
    void doResample();
    void doResampleAVFrame();
private:
};

#endif /* audio_resample_hpp */

上面doResample()對應(yīng)swr_convert()函數(shù)外里,doResampleAVFrame()函數(shù)對應(yīng)
swr_convert_frame()函數(shù)

  • 具體實(shí)現(xiàn):
/** av_frame_get_buffer()給AVFrame分配的音頻內(nèi)存和av_samples_alloc_array_and_samples()分配的音頻內(nèi)存塊的區(qū)別
 *  1怎爵、前者內(nèi)存由引用計(jì)數(shù)管理,而且對于planner方式音頻盅蝗,是兩個(gè)獨(dú)立的內(nèi)存塊鳖链,內(nèi)存塊首地址不一樣;
 *  2墩莫、后者內(nèi)存和普通的內(nèi)存管理方式一樣芙委,需要自己手動(dòng)釋放,對于planner方式音頻狂秦,它也是一個(gè)連續(xù)的內(nèi)存塊题山,只不過每個(gè)planner對應(yīng)的首地址分別存儲于
 *  指向的數(shù)組指針中;釋放由函數(shù)av_freep(dataAddr)故痊,dataAddr指的連續(xù)內(nèi)存塊的首地址
 */
/** 重采樣實(shí)驗(yàn):
 *  1、44100的采樣率玖姑,升采樣48000或者降采樣24000 后對音質(zhì)影響不大愕秫,存儲到文件中的數(shù)值會變化
 *  2、float類型的采樣數(shù)據(jù)焰络,轉(zhuǎn)換成32位整數(shù)型采樣數(shù)據(jù) 對音質(zhì)影響也不大戴甩,存儲到文件中的數(shù)值會變化
 */
void AudioResample::doResample()
{
    string curFile(__FILE__);
    unsigned long pos = curFile.find("ffmpeg-demo");
    if (pos == string::npos) {  // 未找到
        return;
    }
    string resouceDic = curFile.substr(0,pos)+"ffmpeg-demo/filesources/";
    
    // 原pcm文件中存儲的是float類型音頻數(shù)據(jù),小端序闪彼;packet方式
    string pcmpath = resouceDic+"test_441_f32le_2.pcm";
    string dstpath1 = "test_240_s32le_2.pcm";
    
    FILE *srcFile = fopen(pcmpath.c_str(), "rb+");
    if (srcFile == NULL) {
        LOGD("fopen srcFile fail");
        return;
    }
    FILE *dstFile = fopen(dstpath1.c_str(), "wb+");
    if (dstFile == NULL) {
        LOGD("fopen dstFile fail");
        return;
    }
    
    // 音頻數(shù)據(jù)
    uint8_t **src_data,**dst_data;
    // 聲道數(shù)
    int src_nb_channels=0,dst_nb_channels=0;
    int64_t src_ch_layout = AV_CH_LAYOUT_STEREO,dst_ch_layout = AV_CH_LAYOUT_STEREO;
    // 采樣率
    int64_t src_rate = 44100,dst_rate = 48000;
    // 采樣格式和音頻數(shù)據(jù)存儲方式
    const enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT,dst_sample_fmt = AV_SAMPLE_FMT_S32P;
    int src_nb_samples=1024,dst_nb_samples,max_dst_nb_samples;
    int src_linesize,dst_linesize;
    
    // 1甜孤、創(chuàng)建上下文SwrContext
    SwrContext *swrCtx;
    swrCtx = swr_alloc();
    if (swrCtx == NULL) {
        LOGD("swr_allock fail");
        return;
    }
    
    // 2、設(shè)置重采樣的相關(guān)參數(shù) 這些函數(shù)位于頭文件 <libavutil/opt.h>
    av_opt_set_int(swrCtx, "in_channel_layout", src_ch_layout, 0);
    av_opt_set_int(swrCtx, "in_sample_rate", src_rate, 0);
    av_opt_set_sample_fmt(swrCtx, "in_sample_fmt", src_sample_fmt, 0);
    
    av_opt_set_int(swrCtx, "out_channel_layout", dst_ch_layout, 0);
    av_opt_set_int(swrCtx, "out_sample_rate", dst_rate, 0);
    av_opt_set_sample_fmt(swrCtx, "out_sample_fmt", dst_sample_fmt, 0);
    
    // 3畏腕、初始化上下文
    int ret = 0;
    ret = swr_init(swrCtx);
    if (ret < 0) {
        LOGD("swr_init fail");
        return;
    }
    
    src_nb_channels = av_get_channel_layout_nb_channels(src_ch_layout);
    dst_nb_channels = av_get_channel_layout_nb_channels(dst_ch_layout);
    // 根據(jù)src_sample_fmt缴川、src_nb_samples、src_nb_channels為src_data分配內(nèi)存空間描馅,和設(shè)置對應(yīng)的的linesize的值把夸;返回分配的總內(nèi)存的大小
    int src_buf_size = av_samples_alloc_array_and_samples(&src_data, &src_linesize, src_nb_channels, src_nb_samples, src_sample_fmt, 0);
    // 根據(jù)src_nb_samples*dst_rate/src_rate公式初步估算重采樣后音頻的nb_samples大小
    max_dst_nb_samples = dst_nb_samples = (int)av_rescale_rnd(src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
    int dst_buf_size = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);
    
    size_t read_size = 0;
    while ((read_size = fread(src_data[0], 1, src_buf_size, srcFile)) > 0) {
        
        /** 因?yàn)橛修D(zhuǎn)換時(shí)有緩存,所以要不停的調(diào)整轉(zhuǎn)換后的內(nèi)存的大小铭污。估算重采樣后的的nb_samples的大小恋日,這里swr_get_delay()用于獲取重采樣的緩沖延遲
         *  dst_nb_samples的值會經(jīng)過多次調(diào)整后區(qū)域穩(wěn)定
         */
        dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(swrCtx, src_rate)+src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
        if (dst_nb_samples > max_dst_nb_samples) {
            LOGD("要重新分配內(nèi)存了");
            // 先釋放以前的內(nèi)存,不管sample_fmt是planner還是packet方式嘹狞,av_samples_alloc_array_and_samples()函數(shù)都是分配的一整塊連續(xù)的內(nèi)存
            av_freep(&dst_data[0]);
            // 再重新分配內(nèi)存
            dst_buf_size = av_samples_alloc_array_and_samples(&dst_data, &dst_linesize, dst_nb_channels, dst_nb_samples, dst_sample_fmt, 0);
            max_dst_nb_samples = dst_nb_samples;
        }

        // 開始重采樣岂膳,重采樣后的數(shù)據(jù)將根據(jù)前面指定的存儲方式寫入ds_data內(nèi)存塊中,返回每個(gè)聲道實(shí)際的采樣數(shù)
        /**
         *  遇到問題:轉(zhuǎn)換后聲音不對
         *  原因分析:swr_convert()函數(shù)返回的result是實(shí)際轉(zhuǎn)換的采樣個(gè)數(shù)磅网,該值肯定小于等于預(yù)計(jì)采樣數(shù)dst_nb_samples谈截,所以寫入文件的時(shí)候不能用dst_nb_samples的
         *  值,而應(yīng)該用result值
         **/
        int result = swr_convert(swrCtx, dst_data, dst_nb_samples, (const uint8_t **)src_data, src_nb_samples);
        if (result < 0) {
            LOGD("swr_convert fail %d",result);
            break;
        }
        LOGD("read_size %d dst_nb_samples %d src_nb_samples %d result %d",read_size,dst_nb_samples,src_nb_samples,result);
        // 將音頻數(shù)據(jù)寫入pcm文件
        if (av_sample_fmt_is_planar(dst_sample_fmt)) {  // planner方式,而pcm文件寫入時(shí)一般都是packet方式傻盟,所以這里要注意轉(zhuǎn)換一下
            int size = av_get_bytes_per_sample(dst_sample_fmt);
            // 這里必須是result速蕊,而不能用dst_nb_samples,因?yàn)閞esult才代表此次實(shí)際轉(zhuǎn)換的采樣數(shù) 它肯定小于等于dst_nb_samples
            for (int i=0; i<result;i++) {
                for (int j=0; j<dst_nb_channels; j++) {
                    fwrite(dst_data[j]+i*size, 1, size, dstFile);
                }
            }
        } else {
            // 最后一個(gè)參數(shù)必須為1 否則會因?yàn)閏pu對齊算出來的大小大于實(shí)際的數(shù)據(jù)大小 導(dǎo)致多寫入數(shù)據(jù) 從而造成錯(cuò)誤
            dst_buf_size = av_samples_get_buffer_size(&dst_linesize,dst_nb_channels, result,dst_sample_fmt,1);
            fwrite(dst_data[0], 1, dst_buf_size, dstFile);
        }
    }
    
    // 還有剩余的緩存數(shù)據(jù)沒有轉(zhuǎn)換娘赴,第三個(gè)傳遞NULL规哲,第四個(gè)傳遞0即可將緩存中的全部取出
    do {
        int real_nb_samples = swr_convert(swrCtx,dst_data,dst_nb_samples,NULL,0);
        if (real_nb_samples <=0) {
            break;
        }
        LOGD("余量 %d",real_nb_samples);
        if (av_sample_fmt_is_planar(dst_sample_fmt)) {
            int size = av_get_bytes_per_sample(dst_sample_fmt);
            for (int i=0; i<real_nb_samples; i++) {
                for (int j=0; j<dst_nb_channels; j++) {
                    fwrite(dst_data[j]+i*size,1,size,dstFile);
                }
            }
        } else {
            int size = av_samples_get_buffer_size(NULL,dst_nb_channels,real_nb_samples,dst_sample_fmt,1);
            fwrite(dst_data[0], 1, size, dstFile);
        }
        
    } while (true);
    
    // 釋放資源
    av_freep(&src_data[0]);
    av_freep(&dst_data[0]);
    swr_free(&swrCtx);
    fclose(srcFile);
    fclose(dstFile);
}
  • 代碼解讀
    1、要進(jìn)行轉(zhuǎn)換的PCM數(shù)據(jù)位于項(xiàng)目同級目錄下的filesources文件夾中诽表,這里采用的是test_441_f32le_2.pcm唉锌,代表采樣格式float類型小端序,采樣率44100 聲道數(shù)2的數(shù)據(jù)
    2竿奏、src_channel_layout,src_sample_rate,src_sample_fmt三個(gè)變量要與步驟1中的對應(yīng)起來袄简。dst_channel_layout,dst_sample_rate,dst_sample_fmt則可以按照自己的需求進(jìn)行轉(zhuǎn)換
    3、代碼 dst_nb_samples = (int)av_rescale_rnd(swr_get_delay(swrCtx, src_rate)+src_nb_samples, dst_rate, src_rate, AV_ROUND_UP);
    為不停調(diào)整轉(zhuǎn)后后的預(yù)計(jì)目標(biāo)采樣數(shù)大小泛啸,此代碼可有可無绿语,不是必須的
    4、swr_convert()函數(shù)返回的是實(shí)際轉(zhuǎn)換的目標(biāo)采樣數(shù)大小候址,所以dst_data中數(shù)據(jù)的大小應(yīng)該是該函數(shù)返回的值吕粹,而非前面通過dst_nb_samples計(jì)算出來的值
    5、轉(zhuǎn)換過程中可能會有部分為轉(zhuǎn)換的數(shù)據(jù)存在于內(nèi)部緩沖中岗仑,通過int real_nb_samples = swr_convert(swrCtx,dst_data,dst_nb_samples,NULL,0);
    代碼將這些剩余的數(shù)據(jù)轉(zhuǎn)換完畢
void AudioResample::doResampleAVFrame()
{
    string curFile(__FILE__);
    unsigned long pos = curFile.find("ffmpeg-demo");
    if (pos == string::npos){
        LOGD("cannot find file");
        return;
    }
    string srcpcm = curFile.substr(0,pos)+"ffmpeg-demo/filesources/test_441_f32le_2.pcm";
    string dstpcm = "test_441_s32le_2.pcm";
    uint64_t src_channel_layout = AV_CH_LAYOUT_STEREO,dst_channel_layout = AV_CH_LAYOUT_STEREO;
    int src_nb_channels = av_get_channel_layout_nb_channels(src_channel_layout);
    int dst_nb_channels = av_get_channel_layout_nb_channels(dst_channel_layout);
    int64_t src_nb_samples = 1024,dst_nb_samples,max_dst_nb_samples;
    int64_t src_sample_rate = 44100,dst_sample_rate=44100;
    const enum AVSampleFormat src_sample_fmt = AV_SAMPLE_FMT_FLT,dst_sample_fmt = AV_SAMPLE_FMT_S32P;

    FILE *srcFile,*dstFile;
    if ((srcFile = fopen(srcpcm.c_str(), "rb")) == NULL) {
        LOGD("open srcpcm fail");
        return;
    }
    if ((dstFile = fopen(dstpcm.c_str(),"wb+")) == NULL) {
        LOGD("open dstpcm fail");
        return;
    }

    AVFrame *srcFrame,*dstFrame;
    srcFrame = av_frame_alloc();
    srcFrame->nb_samples = (int)src_nb_samples;
    srcFrame->format = src_sample_fmt;
    srcFrame->sample_rate = (int)src_sample_rate;
    srcFrame->channel_layout = src_channel_layout;
    srcFrame->channels = src_nb_channels;
    int ret = av_frame_get_buffer(srcFrame,0);
    if (ret < 0) {
        LOGD("av_frame_get_buffer() return faile %d",ret);
        return;
    }
    ret = av_frame_make_writable(srcFrame);
    if (ret < 0) {
        LOGD("av_frame_make_writable() fail %d",ret);
        return;
    }

    // 采樣數(shù)跟采樣率有關(guān) 目標(biāo)采樣數(shù)根據(jù)由采樣率根據(jù)比例公式計(jì)算出
    // R2 = R1*(β2/β1) R1 稱作參考源 與R2是同一類型數(shù)據(jù)匹耕,β1、β2稱作比例因子荠雕,影響系數(shù)稳其。R1,β2炸卑,β1一次對應(yīng)這個(gè)公式的第一二三個(gè)系數(shù)
    max_dst_nb_samples = dst_nb_samples = (int)av_rescale_rnd(src_nb_samples,dst_sample_rate,src_sample_rate,AV_ROUND_UP);

    dstFrame = av_frame_alloc();
    dstFrame->sample_rate = (int)dst_sample_rate;
    dstFrame->format = dst_sample_fmt;
    dstFrame->nb_samples = (int)dst_nb_samples;
    dstFrame->channel_layout = dst_channel_layout;
    dstFrame->channels = dst_nb_channels;
    ret = av_frame_get_buffer(dstFrame,0);
    if(ret < 0) {
        LOGD("av_frame_get_buffer2 failt %d",ret);
        return;
    }
    ret = av_frame_make_writable(dstFrame);
    if (ret < 0){
        LOGD("av_frame_make_writable() 2 failt %d",ret);
        return;
    }
    
    // 1既鞠、創(chuàng)建上下文
    SwrContext *swr_ctx = swr_alloc();
    if (swr_ctx == NULL) {
        LOGD("swr_alloc fail");
        return;
    }
    
    // 2、設(shè)置轉(zhuǎn)換參數(shù) 在<libswresample/options.c>文件中可以找到定義
    av_opt_set_int(swr_ctx,"in_channel_layout",src_channel_layout,AV_OPT_SEARCH_CHILDREN);
    av_opt_set_int(swr_ctx,"in_sample_rate",src_sample_rate,AV_OPT_SEARCH_CHILDREN);
    av_opt_set_sample_fmt(swr_ctx,"in_sample_fmt",src_sample_fmt,AV_OPT_SEARCH_CHILDREN);
    av_opt_set_int(swr_ctx,"out_channel_layout",dst_channel_layout,AV_OPT_SEARCH_CHILDREN);
    av_opt_set_int(swr_ctx,"out_sample_rate",dst_sample_rate,AV_OPT_SEARCH_CHILDREN);
    av_opt_set_sample_fmt(swr_ctx,"out_sample_fmt",dst_sample_fmt,AV_OPT_SEARCH_CHILDREN);
    
    // 3矾兜、執(zhí)行初始化
    ret = swr_init(swr_ctx);
    if (ret < 0) {
        LOGD("swr_init fail");
        return;
    }
    
    int require_size = av_samples_get_buffer_size(NULL,src_nb_channels,(int)src_nb_samples,src_sample_fmt,0);
    size_t read_size = 0;
    // 這里必須傳遞srcFrame->data[0]這個(gè)地址损趋,而不能傳遞srcFrame->data
    while ((read_size = fread(srcFrame->data[0],1,require_size,srcFile)) > 0) {
        
        /** 當(dāng)轉(zhuǎn)換后的采樣率大于轉(zhuǎn)換前的轉(zhuǎn)換率時(shí),內(nèi)部會有緩沖椅寺,所以預(yù)計(jì)轉(zhuǎn)換后的目標(biāo)采樣數(shù)會一直變化 所以這里目標(biāo)音頻內(nèi)存大小也要跟著動(dòng)態(tài)調(diào)整浑槽;當(dāng)然也可以不做調(diào)整
         */
        dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx,src_sample_rate)+src_nb_samples,dst_sample_rate,src_sample_rate,AV_ROUND_UP);
        if (dst_nb_samples > max_dst_nb_samples) {
            max_dst_nb_samples = dst_nb_samples;
            
            av_frame_unref(dstFrame);
            LOGD("重新調(diào)整轉(zhuǎn)換后存儲音頻數(shù)據(jù)內(nèi)存大小 %d",max_dst_nb_samples);
            AVFrame *tmp = av_frame_alloc();
            tmp->format = dst_sample_fmt;
            tmp->nb_samples = (int)dst_nb_samples;
            tmp->channel_layout = dst_channel_layout;
            tmp->channels = dst_nb_channels;
            tmp->sample_rate = (int)dst_sample_rate;
            
            ret = av_frame_get_buffer(tmp,0);
            if (ret < 0) {
                LOGD("av_frame_get_buffer fail %d",ret);
                break;
            }
            ret = av_frame_make_writable(tmp);
            if (ret < 0) {
                LOGD("av_frame_make_writable()11 fail %d",ret);
                break;
            }
            av_frame_move_ref(dstFrame, tmp);
        }
        
        // 4、音頻格式轉(zhuǎn)換 返回實(shí)際轉(zhuǎn)換的采樣數(shù) <= dst_nb_samples
        // 對于音頻 AVFrame中extended_data和data屬性指向同一塊內(nèi)存
        int real_convert_nb_samples;
#define UseAVFrame
#ifndef UseAVFrame
        const uint8_t **srcdata = (const uint8_t **)srcFrame->extended_data;
        uint8_t **outdata = dstFrame->extended_data;
        real_convert_nb_samples = swr_convert(swr_ctx,outdata,(int)dst_nb_samples,srcdata,(int)src_nb_samples);
        LOGD("swr_convert nb_samples %d",real_convert_nb_samples);
#else
        ret = swr_convert_frame(swr_ctx,dstFrame,srcFrame);
        if (ret < 0) {
            LOGD("swr_convert_frame fail %d",ret);
            continue;
        }
        real_convert_nb_samples = dstFrame->nb_samples;
        LOGD("swr_convert_frame nb_samples %d",real_convert_nb_samples);
#endif

        // 5返帕、存儲;由于文件是planner格式桐玻,所以對于planner格式的存儲要注意下
        if (av_sample_fmt_is_planar(dst_sample_fmt)) {
            int size = av_get_bytes_per_sample(dst_sample_fmt);
            for (int i =0; i< real_convert_nb_samples;i++) {
                for (int j=0;j<dst_nb_channels;j++) {
                    fwrite(dstFrame->data[j]+i*size,1,size,dstFile);
                }
            }
        } else {
            int size = av_samples_get_buffer_size(NULL,dst_nb_channels,real_convert_nb_samples,dst_sample_fmt,1);
            fwrite(dstFrame->data[0],1,size,dstFile);
        }

    }
    
/** 當(dāng)轉(zhuǎn)換后的采樣率大于轉(zhuǎn)換前的轉(zhuǎn)換率時(shí),內(nèi)部會有緩沖
 */
#ifdef UseAVFrame
    // 會有內(nèi)部緩沖荆萤,這里傳遞NULL镊靴,將剩余的數(shù)據(jù)全部讀取出來
    while (swr_convert_frame(swr_ctx,dstFrame,NULL) >= 0) {
        if (dstFrame->nb_samples <= 0) {
            break;
        }
        LOGD("還有余量 %d",dstFrame->nb_samples);
#else
    uint8_t **outdata = dstFrame->extended_data;
    int real_convert_nb_samples = 1;
        
    while (real_convert_nb_samples > 0) {
        // 會有內(nèi)部緩沖铣卡,這里第三個(gè)傳遞NULL,第四個(gè)傳遞0即可偏竟。將剩余的數(shù)據(jù)全部讀取出來
        real_convert_nb_samples = swr_convert(swr_ctx,outdata,(int)dst_nb_samples,NULL,0);
        LOGD("還有余量 %d",real_convert_nb_samples);
#endif
        if (av_sample_fmt_is_planar(dst_sample_fmt)) {
            int size = av_get_bytes_per_sample((dst_sample_fmt));
            for (int i=0; i<dstFrame->nb_samples; i++) {
                for (int j=0; j<dstFrame->channels; j++) {
                    fwrite(dstFrame->data[j]+i*size,1,size,dstFile);
                }
            }
        } else {
            int size = av_samples_get_buffer_size(NULL,dstFrame->channels,dstFrame->nb_samples,(enum AVSampleFormat)dstFrame->format,1);
            fwrite(dstFrame->data[0],1,size,dstFile);
        }
    }
    
    swr_free(&swr_ctx);
    av_frame_unref(srcFrame);
    av_frame_unref(dstFrame);
    fclose(srcFile);
    fclose(dstFile);
}

遇到問題

1煮落、轉(zhuǎn)換后聲音不對
原因分析:
swr_convert()函數(shù)返回的result是實(shí)際轉(zhuǎn)換的采樣個(gè)數(shù),該值肯定小于等于預(yù)計(jì)采樣數(shù)dst_nb_samples踊谋,所以寫入文件的時(shí)候不能用dst_nb_samples的值蝉仇,而應(yīng)該用result值
具體參考代碼

項(xiàng)目地址

項(xiàng)目地址

代碼位于cppsrc目錄下
audio_resample.hpp
audio_resample.cpp

項(xiàng)目下示例可運(yùn)行于iOS/android/mac平臺,工程分別位于demo-ios/demo-android/demo-mac三個(gè)目錄下殖蚕,可根據(jù)需要選擇不同平臺

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轿衔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子睦疫,更是在濱河造成了極大的恐慌害驹,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蛤育,死亡現(xiàn)場離奇詭異宛官,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瓦糕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門摘刑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刻坊,你說我怎么就攤上這事〉辰” “怎么了谭胚?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長未玻。 經(jīng)常有香客問我灾而,道長,這世上最難降的妖魔是什么扳剿? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任旁趟,我火速辦了婚禮,結(jié)果婚禮上庇绽,老公的妹妹穿的比我還像新娘锡搜。我一直安慰自己,他們只是感情好瞧掺,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布耕餐。 她就那樣靜靜地躺著,像睡著了一般辟狈。 火紅的嫁衣襯著肌膚如雪肠缔。 梳的紋絲不亂的頭發(fā)上夏跷,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天,我揣著相機(jī)與錄音明未,去河邊找鬼槽华。 笑死,一個(gè)胖子當(dāng)著我的面吹牛趟妥,可吹牛的內(nèi)容都是我干的猫态。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼煮纵,長吁一口氣:“原來是場噩夢啊……” “哼懂鸵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起行疏,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤匆光,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酿联,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體终息,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年贞让,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了周崭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喳张,死狀恐怖续镇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情销部,我是刑警寧澤摸航,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站舅桩,受9級特大地震影響酱虎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜擂涛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一读串、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撒妈,春花似錦恢暖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至挟阻,卻和暖如春琼娘,著一層夾襖步出監(jiān)牢的瞬間峭弟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工脱拼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞒瘸,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓熄浓,卻偏偏與公主長得像情臭,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子赌蔑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359