?目錄
?一、FFMPEG 音頻 API
1.1 解碼步驟
????????創建核心上下文指針
????????打開輸入流
????????獲取輸入流
????????獲取解碼器
????????初始化解碼器
????????創建輸入流指針
????????創建輸出流指針
????????初始化 SDL
????????配置音頻參數
????????打開音頻設備
????????獲取一幀數據
????????發送給解碼器
????????從解碼器獲取數據
????????開辟數據空間
????????初始化內存????????
????????音頻重采樣配置 --- 相當于視頻的格式轉換
????????由通道數獲取默認的通道布局
????????初始化重采樣核心結構體
????????音頻重采樣
????????播放
????????延時
1.2 參數擴展
????????SDL_AudioSpec
????????AVFrame
二、FFMPEG 錄制聲音的過程
2.1 步驟
????????FFMPEG 注冊所有??
????????FFMPEG 核心上下文申請
????????查找音頻設備
????????注冊音頻設備
????????SDL 初始化
????????配置音頻參數
????????打開音頻設備
????????讀取一幀數據
????????寫入到文件
三、如何在板子上實現
一、FFMPEG 音頻 API
1.1 解碼步驟
????????創建核心上下文指針
????????????????AVFormatContext * avfmtctx? = avformat_alloc_context();
????????打開輸入流
????????????????avformat_open_input(&avfmtctx, argv[1], NULL, NULL);
????????獲取輸入流
????????????????avformat_find_stream_info(avfmtctx, NULL);
????????獲取解碼器
????????????????AVCodecContext * avcodectx = avfmtctx->streams[0]->codec;
? ? ? ? ? ? ? ? AVCodec *avcodec = avcodec_find_decoder(avcodectx->codec_id);
????????初始化解碼器
????????????????avcodec_open2(avcodectx, avcodec, NULL);?
????????創建輸入流指針
????????????????AVPacket * avpkt = av_packet_alloc();
????????創建輸出流指針
????????????????AVFrame * avfrm = av_frame_alloc();?
????????初始化 SDL
????????函數頭文件
????????????????#include <SDL2/SDL.h>
????????函數原型
????????????????int?SDL_Init(Uint32 flags)
????????函數參數
????????????????常用參數
????????????????????????SDL_INIT_TIMER
????????????????????????SDL_INIT_AUDIO
????????????????????????SDL_INIT_VIDEO
????????函數返回值
????????????????成功時返回0,失敗時返回負數錯誤碼; 調用SDL_GetError()可以獲得本次異常信息。
????????配置音頻參數
????????函數原型
????????????????int?SDL_OpenAudioDevice(const char ?*device,int iscapture,const SDL_AudioSpec *desired, SDL_AudioSpec *obtained,int allowed_changes); ?
????????函數參數
????????????????device:音頻設備的名稱,NULL表示使用默認設備
????????????????iscapture:設為0,非0的值在當前SDL2版本還不支持
????????????????desired:期望得到的音頻輸出格式
????????????????obtained:實際的輸出格式
????????????????allowed_changes:該參數用來指定?當期望和實際的不一樣時,能不能夠對某一些輸出參數進行修改。 設為0,則不能修改。設為如下的值,則可對相應的參數修改:
????????????????????????SDL_AUDIO_ALLOW_FREQUENCY_CHANGE
????????????????????????SDL_AUDIO_ALLOW_FORMAT_CHANGE
????????????????????????SDL_AUDIO_ALLOW_CHANNELS_CHANGE
????????????????????????SDL_AUDIO_ALLOW_ANY_CHANGE
????????函數返回值
????????????????0失敗;成功返回有效音頻設備號 >= 2
????????打開音頻設備
????????函數原型
????????????????void SDLCALL SDL_PauseAudioDevice(SDL_AudioDeviceID dev, int pause_on)
????????函數參數
????????????????dev:SDL_OpenAudioDevice函數返回值
????????????????pause_on:根據介紹,填0即可
????????av_read_frame
????????avcodec_send_packet
????????avcodec_receive_frame
????????獲取一幀數據
????????????????av_read_frame(avfmtctx, avpkt)
????????發送給解碼器
????????????????avcodec_send_packet(avcodectx, avpkt);
????????從解碼器獲取數據
????????????????avcodec_receive_frame(avcodectx, avfrm);
????????開辟數據空間
????????函數原型
????????????????int av_samples_get_buffer_size(int *linesize, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align)
????????函數參數
????????????????linesize:計算的lineize,可能為NULL
????????????????nb_channels:聲道數
????????????????SDL_AudioSpec結構體中channels成員變量
????????????????nb_samples:單個通道中的樣本數
????????????????????????采樣頻率(Hz) *當前幀的音頻采樣數/當前幀的音頻數據的采樣率
????????????????sample_fmt:樣本格式
????????????????align:對齊緩沖區大小對齊(0 =默認,1 =無對齊)
????????函數返回值
????????????????需要的緩沖區大小,或失敗時出現負錯誤代碼
????????初始化內存????????
? ? ? ??? ? ? ??調用av_malloc,然后再將內存內容清零
????????函數原型
????????????????void *av_mallocz(size_t size)
????????????????申請數據存放空間
????????音頻重采樣配置 --- 相當于視頻的格式轉換
????????根據輸入和輸出參數,并設置相關選項
????????函數頭文件
????????????????#include "libswresample/swresample.h"
????????函數原型
????????SwrContext *swr_alloc_set_opts(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)
????????函數參數
s:可選的現有SwrContext,如果不為NULL,則會使用該現有上下文。out_ch_layout:輸出聲道布局(Channel Layout)通過函數av_get_default_channel_layout獲取根據SDL_AudioSpec的channels獲取out_sample_fmt:輸出采樣格式根據SDL_AudioSpec的format成員選取out_sample_rate:輸出采樣率SDL_AudioSpec的freq成員in_ch_layout:輸入聲道布局通過函數av_get_default_channel_layout獲取輸入流codecpar下的channelsin_sample_fmt:輸入采樣格式輸入流codec下的sample_fmtin_sample_rate:輸入采樣率輸入流codec下的sample_ratelog_offset:日志偏移量,填0即可log_ctx:日志上下文,填NULL即可
????????由通道數獲取默認的通道布局
????????函數原型
????????????????int64_t av_get_default_channel_layout(int nb_channels);? ??
????????初始化重采樣核心結構體
????????函數原型
????????????????int swr_init(struct SwrContext *s);
????????函數參數
????????????????s:swr_alloc_set_opts返回值
????????音頻重采樣
????????????????針對每一幀音頻的處理。把一幀幀的音頻作相應的重采樣
????????函數原型
????????????????int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in, int in_count);
s:音頻重采樣的上下文out:輸出的指針。傳遞的輸出的數組out_count:輸出的樣本數量,不是字節數。單通道的樣本數量。av_samples_get_buffer_size參數nb_samplesin:輸入的數組,AVFrame解碼出來的DATA(成員extended_data)in_count:輸入的單通道的樣本數量AVFrame結構體的nb_samples成員
????????函數返回值
????????????????每個通道輸出的樣本數,失敗為負值
????????播放
????????函數功能
????????????????使用此函數可以在回調設備(即使用了第一套API需要回調函數填充數據的設備)上緩存更多音頻,而不必通過回調函數填充音頻數據。
????????函數原型
????????????????int SDL_QueueAudio(SDL_AudioDeviceID dev, const void* data, Uint32 len)
????????函數參數
????????????????dev:設備ID
????????????????data:需要被填充的數據指針
????????????????len:數據buffer長度,byte為單位
????????????????????????通過函數av_samples_get_buffer_size獲取
????????函數返回值
????????????????0表示成功,非零表示出現異常
????????//av_samples_get_buffer_size
????????延時
????????SDL_Delay
????????SDL_Delay((輸出數據大小) * 1000.0 / (音頻采樣率?* av_get_bytes_per_sample(音頻格式) * 通道數量) - 1);
1.2 參數擴展
????????SDL_AudioSpec
int freq; freq 每秒鐘發送給音頻設備的sample frame的個數,通常是11025,220502,44100和48000。(sample frame = 樣本精度 * 通道數)//輸入流codec中sample_rate成員
SDL_AudioFormat format; fromat 每個樣本占用的空間大小及格式,例如 AUDIO_S16SYS,樣本是有符號的16位整數,字節順序(大端還是小端)和系統一樣。更多的格式可參考SDL_AudioFormat。// AUDIO_F32SYS
Uint8 channels; channels 通道數,在SDL2.0中支持1(mono),2(stereo),4(quad)和6(5.1)//輸入流codecpar中channels成員
Uint8 silence; silence 音頻數據中表示靜音的值是多少//填0即可
Uint16 samples; 這是每次讀取的采樣數量,?決定了音頻數據回調的頻率。?例如,?設置為1024時,?表示每次讀取1024個樣本數據,?回調函數被調用一次。?這個值不一定是2的冪指數次方,?最好由AVFrame->nb_samples參數賦值。?// 512Uint16 padding; 對于某些環境需要Uint32 size; size 緩沖區的大小(字節為單位),當我們想要更多聲音的時候,我們想讓SDL給出來的聲音緩沖區的尺寸。一個比較合適的值在512到8192之間;ffplay使用1024SDL_AudioCallback callback; /**< Callback that feeds the audio device (NULL to use SDL_QueueAudio()). */ callback用來音頻設備緩沖區的回調函數
void *userdata; userdata在回調函數中使用的數據指針
????????AVFrame
typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8uint8_t *data[AV_NUM_DATA_POINTERS]; // 存放媒體數據的指針數組int linesize[AV_NUM_DATA_POINTERS]; // 視頻或音頻幀數據的行寬uint8_t **extended_data; // 音頻或視頻數據的指針數組。int width, height; // 視頻幀的款和高/*** number of audio samples (per channel) described by this frame*/int nb_samples; // 當前幀的音頻采樣數(每個通道)int format; // 視頻幀的像素格式,見enum AVPixelFormat,或音頻的采樣格式,見enum AVSampleFormaint key_frame; // 當前幀是否為關鍵幀,1表示是,0表示不是。AVRational sample_aspect_ratio; // 視頻幀的樣本寬高比int64_t pts; // 以time_base為單位的呈現時間戳(應向用戶顯示幀的時間)。int64_t pkt_dts; // 從AVPacket復制而來的dts時間,當沒有pts時間是,pkt_dts可以替代pts。int coded_picture_number; // 按解碼先后排序的,解碼圖像數int display_picture_number; // 按顯示前后排序的,顯示圖像數。int quality; // 幀質量,從1~FF_LAMBDA_MAX之間取之,1表示最好,FF_LAMBDA_MAX之間取之表示最壞。void *opaque; // user的私有數據。int interlaced_frame; // 圖片的內容是隔行掃描的(交錯幀)。int top_field_first; // 如果內容是隔行掃描的,則首先顯示頂部字段。int sample_rate; // 音頻數據的采樣率uint64_t channel_layout; // 音頻數據的通道布局。/*** AVBuffer引用,當前幀數據。 如果所有的元素為NULL,則此幀不是引用計數。 必須連續填充此數組,* 即如果buf [i]為非NULL,j <i,buf[j]也必須為非NULL。** 每個數據平面最多可以有一個AVBuffer,因此對于視頻,此數組始終包含所有引用。 * 對于具有多于AV_NUM_DATA_POINTERS個通道的平面音頻,可能有多個緩沖區可以容納在此陣列中。 * 然后額外的AVBufferRef指針存儲在extended_buf數組中。*/AVBufferRef *buf[AV_NUM_DATA_POINTERS];AVBufferRef **extended_buf; // AVBufferRef的指針int nb_extended_buf; // extended_buf的數量enum AVColorSpace colorspace; // YUV顏色空間類型。int64_t best_effort_timestamp; // 算法預測的timestampint64_t pkt_pos; // 記錄上一個AVPacket輸入解碼器的位置。int64_t pkt_duration; // packet的durationAVDictionary *metadata;int channels; // 音頻的通道數。int pkt_size; // 包含壓縮幀的相應數據包的大小。} AVFrame;
二、FFMPEG 錄制聲音的過程
2.1 步驟
????????FFMPEG 注冊所有??
????????頭文件
????????????????#include "libavdevice/avdevice.h"
? ? ? ??函數原型
????????????????void avdevice_register_all(void)
????????FFMPEG 核心上下文申請
????????????????AVFormatContext * avfmtctx? = avformat_alloc_context();
????????查找音頻設備
????????函數原型
????????????????AVInputFormat *av_find_input_format(const char *short_name)
????????函數參數
????????????????直接填alsa即可
????????函數返回值
????????????????就是需要的輸入設備的核心上下文指針
????????注冊音頻設備
????????函數原型
????????????????int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options)
????????函數參數
????????????????ps:FFMPEG的核心上下文指針
????????????????url:在此需要使用聲卡的名字
????????????????"plughw:CARD=AudioPCI,DEV=0"
????????????????fmt:av_find_input_format函數返回值
????????????????options:填NULL
????????SDL 初始化
????????????????SDL_Init(SDL_INIT_AUDIO);
????????配置音頻參數
????????函數原型
????????????????int SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained)
????????????????????????desired:想要的配置
????????????????????????obtained:實際得到的配置,此處填NULL即可
????????打開音頻設備
????????????????void SDL_PauseAudio(int pause_on)
????????讀取一幀數據
????????????????int av_read_frame(AVFormatContext *s, AVPacket *pkt)
????????寫入到文件
????????????????fwrite
三、如何在板子上實現
1、在 buildroot 中勾選 ffmpeg 選項、SDL 選項
2、編譯文件系統 --- 生成新的文件系統
3、燒錄新的文件系統
4、和之前 LVGL 相同 --- 修改 Makefile
????????4.1 CC 換成 buildroot 的交叉編譯工具
????????4.2 把之前該刪除的依賴庫刪除,把需要的庫給加上
5、編譯
????????可能出現的問題
????????buildroot 支持的 ffmpeg 和 SDL 版本和程序中使用的版本不符
6、運行
代碼
dec_audio.c //音頻
#include <stdio.h>
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
#include <SDL2/SDL.h>int main(int argc, char *argv[])
{if(argc < 2){printf("./play <name>\n");return 0;}//創建核心上下文指針AVFormatContext * avfmtctx = avformat_alloc_context();//打開輸入流avformat_open_input(&avfmtctx, argv[1], NULL, NULL);//獲取輸入流avformat_find_stream_info(avfmtctx, NULL); //到此會獲取//輸入流獲取之后獲取的是純音頻文件,只有一個流 ,所以還用 avfmt->streams[0]//有的音頻,會帶一個圖片封面 --- 這種音頻會報段錯誤//獲取解碼器AVCodecContext * avcodectx = avfmtctx->streams[0]->codec;AVCodec *avcodec = avcodec_find_decoder(avcodectx->codec_id);//初始化解碼器avcodec_open2(avcodectx, avcodec, NULL); //創建輸入流指針AVPacket * avpkt = av_packet_alloc(); //存放輸入流中一幀圖像//創建輸出流指針AVFrame * avfrm = av_frame_alloc(); //初始化SDLSDL_Init(SDL_INIT_AUDIO);//配置音頻參數SDL_AudioSpec desired, obtained; //一個期望的,一個獲得的desired.callback = NULL;desired.channels = 2; //期望的通道數desired.format = AUDIO_S16SYS; //音頻的格式desired.freq = avcodectx->sample_rate; //采樣率 --- 需要注意desired.padding = 0;desired.samples = 1152; //采樣數desired.silence = 0;desired.size = 0; //desired.userdata = NULL;//打開音頻設備SDL_AudioDeviceID aid = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, SDL_AUDIO_ALLOW_ANY_CHANGE); //理論上ID會大于0//啟動音頻設備SDLCALL SDL_PauseAudioDevice(aid, 0);enum AVSampleFormat mysfmt;switch(obtained.format){case AUDIO_S16SYS: mysfmt = AV_SAMPLE_FMT_S16; break;case AUDIO_S32SYS: mysfmt = AV_SAMPLE_FMT_S32; break;case AUDIO_F32SYS: mysfmt = AV_SAMPLE_FMT_FLT; break;}int size;uint8_t *data = NULL;while(1){//獲取一幀數據if(av_read_frame(avfmtctx, avpkt) != 0){printf("獲取文件錯誤/到達文件結尾\n");break;}//發送給解碼器avcodec_send_packet(avcodectx, avpkt);//從解碼器獲取數據avcodec_receive_frame(avcodectx, avfrm); //第一幀和第二幀獲取的大小不一樣 --- 把開辟空間方在里面//開辟數據空間size = av_samples_get_buffer_size(NULL, obtained.channels, avfrm->nb_samples, mysfmt, 0);//初始化內存data = av_mallocz(size);//音頻重采樣配置 --- 相當于視頻的格式轉換struct SwrContext * swrctx = swr_alloc_set_opts(NULL, av_get_default_channel_layout(obtained.channels), mysfmt, obtained.freq, \av_get_default_channel_layout(avcodectx->channels), avcodectx->sample_fmt, avcodectx->sample_rate, 0, NULL);//初始化重采樣核心結構體swr_init(swrctx);//音頻重采樣swr_convert(swrctx, &data, size, avfrm->extended_data, avfrm->nb_samples);//播放SDL_QueueAudio(aid, data, size);//延時//SDL_Delay((輸出數據大小) * 1000.0 / (音頻采樣率 * av_get_bytes_per_sample(音頻格式) * 通道數量) - 1);SDL_Delay((size) * 1000.0 / (obtained.freq * av_get_bytes_per_sample(mysfmt) * obtained.channels) - 1); //網上有兩種說法,1.當前的音頻播放需要時間 2.音頻幀不夠,通過延時,補全av_free(data);}
}
get_audio.c //錄音
#include <stdio.h>
#include "libavformat/avformat.h"
#include <SDL2/SDL.h>
#include <pthread.h>
#include <unistd.h>
#include "libavdevice/avdevice.h"int end_flag = 0;void *pthread_count_func(void *arg)
{int num = 0;while(num--){sleep(1);printf("錄音剩余 %d 秒\n", num);}end_flag = 1;
}int main(void)
{//FFMPEG注冊所有 --- 必須寫avdevice_register_all();//FFMPEG核心上下文申請AVFormatContext * avfmtctx = avformat_alloc_context();//查找音頻設備AVInputFormat * avifmt = av_find_input_format("alsa");//注冊音頻設備avformat_open_input(&avfmtctx, "hw:CARD=AudioPCI,DEV=0", avifmt, NULL);//SDL初始化SDL_Init(SDL_INIT_AUDIO);//配置音頻參數 --- 再此配置為期望的,得到的填NULL即可SDL_AudioSpec desired; //一個期望的,一個獲得的desired.callback = NULL;desired.channels = 2; //期望的通道數desired.format = AUDIO_S16SYS; //音頻的格式desired.freq = 48000; //采樣率 --- 過低聲音會很奇怪desired.padding = 0;desired.samples = 1152; //采樣數desired.silence = 0;desired.size = 0; //desired.userdata = NULL;SDL_OpenAudio(&desired, NULL);//打開音頻設備SDL_PauseAudio(0);AVPacket * avpkt = av_packet_alloc(); //存放輸入流中一幀數據FILE *file = fopen("./9203.pcm", "w");pthread_t pd = 0;pthread_create(&pd, NULL, pthread_count_func, NULL);while(1){if(end_flag){break;}//讀取一幀數據av_read_frame(avfmtctx, avpkt);//寫入到文件fwrite(avpkt->data, 1, avpkt->size, file);}fclose(file);return 0;
}