[4] ffmpeg + SDL2 實現的有雜音的音訊播放器
阿新 • • 發佈:2019-01-10
日期:2016.10.4
作者:isshe
github:github.com/isshe
郵箱:[email protected]
平臺:ubuntu16.04 64bit
前言
- ffmpeg將資料解碼成pcm。
- SDL將pcm資料輸出。
1. 程式流程圖
- 初始化
- 找到相應的解碼器,開啟解碼器
- 開啟音訊裝置(可用SDL_OpenAudioDriver()代替),這裡會開執行緒執行callback函式。
- 設定相關引數。第2,3,4個引數為output, 5,6,7為input引數。
- 讀一個AVPacket.
- 解碼。
- 進行一些格式之類的轉換。
- 輸出。
示例
程式碼
* 這個程式的意圖是:
* 解碼資料放到緩衝區,用全域性變數指向緩衝區。
* 回撥函式(在SDL_AudioOpen()呼叫時開執行緒執行)把緩衝區資料給stream,然後發出聲音。
* 子執行緒中,回撥函式會一次次被呼叫,取走緩衝區資料。
* 而主執行緒就一迴圈解碼資料,放到緩衝區,如果緩衝區還有資料就等待。
* 這份程式碼輸出的音訊有雜音,還未修正。
* 程式是參考雷神的程式碼改的。本來是執行他的程式碼,有這個問題,改了out_buffer也還是有。就按自己想的改了這個,還是這個問題。
* 需要注意的地方:
* 回撥函式的第一個引數,和SDL_AudioSpec結構中的userdata對應。(一般傳一個AVCodecContext結構。
* 192000代表192kHz,一般音訊使用的最高取樣頻率。(音訊相關知識見前一博文,以及度娘)
* 三個全域性變數是在回撥函式中使用,標誌緩衝區位置之類的。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define __STDC_CONSTANT_MACROS //ffmpeg要求
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#ifdef __cplusplus
}
#endif
#define MAX_AUDIO_FRAME_SIZE 192000 //1 second of 48khz 32bit audio
#define FILE_NAME "/home/isshe/Music/WavinFlag.aac"
#define ERR_STREAM stderr
#define OUT_SAMPLE_RATE 44100
static uint8_t *audio_buf;
static int audio_len;
static long long audio_buf_index;
void get_file_name(char *filename, int argc, char *argv[]);
void open_and_find_inputfile_info(AVFormatContext **pformat_ctx, char *filename);
int get_audio_stream_index(AVFormatContext *pformat_ctx);
void fill_audio(void *udata, Uint8 *stream, int len);
int main(int argc, char *argv[])
{
AVFrame out_frame;
AVFormatContext *pformat_ctx = NULL;
int audio_stream = 0;
AVCodecContext *pcodec_ctx = NULL;
AVCodec *pcodec = NULL;
AVPacket *ppacket = NULL; //!
AVFrame *pframe = NULL;
uint8_t *out_buffer = NULL; //
int decode_len = 0;
uint32_t len = 0;
int got_picture = 0;
int index = 0;
int64_t in_channel_layout = 0;
struct SwrContext *swr_ctx = NULL;
char filename[256] = FILE_NAME;
FILE *output_fp = NULL;
int convert_len = 0;
int data_size = 0;
//SDL
SDL_AudioSpec wanted_spec;
get_file_name(filename, argc, argv);
fprintf(ERR_STREAM, "file name: %s\n", filename);
//about ffmpeg
//init
av_register_all();
//SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
{
fprintf(ERR_STREAM, "Couldn't init SDL: %s\n", SDL_GetError());
exit(-1);
}
//open input file
// pformat_ctx = avformat_alloc_context();
open_and_find_inputfile_info(&pformat_ctx, filename);
av_dump_format(pformat_ctx, 0, filename, false); //輸出檔案資訊,十分好用的一個函式
audio_stream = get_audio_stream_index(pformat_ctx);
pcodec_ctx = pformat_ctx->streams[audio_stream]->codec;
//找到一個相應的解碼器
pcodec = avcodec_find_decoder(pcodec_ctx->codec_id);
if (pcodec == NULL)
{
fprintf(ERR_STREAM, "Codec not found\n");
exit(-1);
}
out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRAME_SIZE * 2);
audio_buf = out_buffer;
pframe = av_frame_alloc();
//SDL_AudioSpec,設定引數,用於開啟音訊裝置
//nb_samples: AAC-1024 MP3-1152
//AV_CH_LAYOUT_STEREO: 立體
//樣本格式。在一個enum裡
//取樣率:44100, 每秒有44100個樣本。
//通道數:1-單聲道,2-立體,4-四聲道,6-...
wanted_spec.freq = pcodec_ctx->sample_rate; //樣本率44100
wanted_spec.format = AUDIO_S16SYS; //樣本格式
wanted_spec.channels = pcodec_ctx->channels; //1-單聲道,2-立體
wanted_spec.silence = 0; //靜音值
wanted_spec.samples = 1024; //輸出樣本數
wanted_spec.callback = fill_audio; //回撥函式
wanted_spec.userdata = pcodec_ctx; //使用者資料
//開啟音訊裝置, 成功返回0,可用SDL_OpenAudioDriver代替
if (SDL_OpenAudio(&wanted_spec, NULL) < 0)
{
fprintf(ERR_STREAM, "Couldn't open Audio\n");
exit(-1);
}
//設定以下引數,供解碼後的swr_alloc_set_opts使用。
out_frame.format = AV_SAMPLE_FMT_S16;
out_frame.sample_rate = wanted_spec.freq;
out_frame.channels = wanted_spec.channels;
out_frame.channel_layout = av_get_default_channel_layout(wanted_spec.channels);
//開啟解碼器
if (avcodec_open2(pcodec_ctx, pcodec, NULL) < 0)
{
fprintf(ERR_STREAM, "Couldn't open decoder\n");
exit(-1);
}
fprintf(ERR_STREAM, "Bit rate: %d\n", pformat_ctx->bit_rate);
fprintf(ERR_STREAM, "Decoder Name = %s\n", pcodec_ctx->codec->long_name);
fprintf(ERR_STREAM, "Channels: %d\n", pcodec_ctx->channels);
fprintf(ERR_STREAM, "Sample per Second: %d\n", pcodec_ctx->sample_rate);
ppacket = (AVPacket *)av_malloc(sizeof(AVPacket));
av_init_packet(ppacket);
//播放
SDL_PauseAudio(0); //???
while( av_read_frame(pformat_ctx, ppacket) >= 0 )
{
if (ppacket->stream_index == audio_stream)
{
decode_len = avcodec_decode_audio4(pcodec_ctx, pframe, &got_picture, ppacket);
if (decode_len < 0)
{
fprintf(ERR_STREAM, "Couldn't decode audio frame\n");
continue; //
}
if (got_picture)
{
if (swr_ctx != NULL)
{
swr_free(&swr_ctx);
swr_ctx = NULL;
}
swr_ctx = swr_alloc_set_opts(NULL, out_frame.channel_layout,
(AVSampleFormat)out_frame.format,out_frame.sample_rate,
pframe->channel_layout,(AVSampleFormat)pframe->format,
pframe->sample_rate, 0, NULL);
//初始化
if (swr_ctx == NULL || swr_init(swr_ctx) < 0)
{
fprintf(ERR_STREAM, "swr_init error\n");
break;
}
convert_len = swr_convert(swr_ctx, &audio_buf,
MAX_AUDIO_FRAME_SIZE,
(const uint8_t **)pframe->data,
pframe->nb_samples);
}
printf("decode len = %d, convert_len = %d\n", decode_len, convert_len);
//回到緩衝區頭,繼續播放資料
audio_buf_index = 0;
audio_buf = out_buffer;
//通道數 * 轉換的長度 * 每個樣本的長度
audio_len = out_frame.channels * convert_len * av_get_bytes_per_sample((AVSampleFormat)out_frame.format);
while(audio_len > 0)
{
SDL_Delay(1); //停1微秒
}
}
av_init_packet(ppacket);
// av_free_packet(ppacket);
}
swr_free(&swr_ctx);
SDL_CloseAudio();
SDL_Quit();
fclose(output_fp);
av_free(out_buffer);
avcodec_close(pcodec_ctx);
avformat_close_input(&pformat_ctx);
return 0;
}
void fill_audio(void *udata, Uint8 *stream, int len)
{
SDL_memset(stream, 0, len);
if (audio_len == 0)
{
return ;
}
len = len > audio_len ? audio_len : len;
SDL_MixAudio(stream, (uint8_t*)audio_buf + audio_buf_index, len, SDL_MIX_MAXVOLUME);
audio_buf_index += len;
audio_len -= len;
stream += len;
}
void get_file_name(char *filename, int argc, char *argv[])
{
if (argc == 2)
{
memcpy(filename, argv[1], strlen(argv[1]) + 1);
}
else if (argc > 2)
{
fprintf(ERR_STREAM, "Usage: ./*.out audio_file.mp3\n");
exit(-1);
}
}
void open_and_find_inputfile_info(AVFormatContext **pformat_ctx, char *filename)
{
if (avformat_open_input(pformat_ctx, filename, NULL, NULL) != 0)
{
fprintf(ERR_STREAM, "Couldn' open input file\n");
exit(-1);
}
if (avformat_find_stream_info(*pformat_ctx, NULL) < 0)
{
fprintf(ERR_STREAM, "Couldn' find stream info\n");
exit(-1);
}
}
int get_audio_stream_index(AVFormatContext *pformat_ctx)
{
int i = 0;
int audio_stream = -1;
for (i = 0; i < pformat_ctx->nb_streams; i++)
{
if (pformat_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_stream = i;
break;
}
}
if (audio_stream == -1)
{
fprintf(ERR_STREAM, "Didn't find audio stream\n");
exit(-1);
}
return audio_stream;
}
編譯
- Makefile
CC = g++
LIBDIR = -L/usr/local/lib
INCDIR = -I/usr/local/include/ -I../common/ -I./
OPTION = -O2 -Wall -g
LIB_FFMPEG = -lavformat -lavcodec -lavformat -lavutil -lswresample -lswscale
LIB_SDL2 = -lSDL2 -lSDL2main
LIB_OTHER = -lx264 -lx265 -lvpx -lmp3lame -lopus -lfdk-aac -lX11 -lva -lvdpau -lva-drm -lva-x11 -lvorbisenc -lvorbis -ltheoraenc -ltheoradec -ldl -lm -lpthread -lz
ffmpeg_decoder:
$(CC) audio_player_v1.0.c -o audio_player_v1.0.out $(LIBDIR) $(INCDIR) $(OPTION) $(LIB_FFMPEG) $(LIB_OTHER) $(LIB_SDL2)
結果
- ubuntu16.04輸出的音訊有雜音。windows下則沒聽出來有雜音。
- windows下用的是vs2010執行。具體執行方法,見雷神的視訊。
參考資料
程式碼下載
拓展(待補充)
- ffmpeg視訊相關函式:
- ffmpeg視訊相關資料結構:
- ffmpeg音訊相關函式:
- ffmpeg音訊相關資料結構:
- SDL視訊相關函式:
- SDL視訊相關資料結構:
- SDL音訊相關函式:
- SDL音訊相關資料結構: