[4] ffmpeg + SDL2 实现的有杂音的音频播放器


日期:2016.10.4
作者:isshe
github:github.com/isshe
邮箱:[email protected]
平台:ubuntu16.04 64bit


前言

  • ffmpeg将数据解码成pcm。
  • SDL将pcm数据输出。

1. 程序流程图

[4] ffmpeg + SDL2 实现的有杂音的音频播放器_第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 
#include 
#include 

#define __STDC_CONSTANT_MACROS      //ffmpeg要求

#ifdef __cplusplus
extern "C"
{
#endif

#include 
#include 
#include 
#include 

#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运行。具体运行方法,见雷神的视频。

参考资料

雷神博客:http://blog.csdn.net/leixiaohua1020/article/details/38979615?locationNum=2

代码下载

参考雷神的:http://download.csdn.net/detail/i_scream_/9645560
自己改的:http://download.csdn.net/detail/i_scream_/9645676

拓展(待补充)

  • ffmpeg视频相关函数:
  • ffmpeg视频相关数据结构:
  • ffmpeg音频相关函数:
  • ffmpeg音频相关数据结构:
  • SDL视频相关函数:
  • SDL视频相关数据结构:
  • SDL音频相关函数:
  • SDL音频相关数据结构:

你可能感兴趣的:(「初探」ffmpeg)