音頻解碼與編碼流程
解碼流程
音頻編解碼流程與視頻編解碼流程一致广匙,我們可以對 mp4 文件的音頻流進行解碼,并將解碼后的音頻數(shù)據(jù)保存到 PCM 文件中,后續(xù)我們可以通過讀取 PCM 文件中的數(shù)據(jù)實現(xiàn)音頻流的編碼操作
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
- format(音頻為
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_rate
和format
參數(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
窃诉,可以簡化代碼和增加代碼的健壯性