1. 程式人生 > >[4] ffmpeg + SDL2 實現的有雜音的音訊播放器

[4] ffmpeg + SDL2 實現的有雜音的音訊播放器

日期: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音訊相關資料結構: