zoukankan      html  css  js  c++  java
  • ffmpeg+SDL2实现的音频播放器V2.0(无杂音)

    1. 前言

    目前为止,学习了并记录了ffmpeg+SDL2显示视频以及事件(event)的内容。 
    这篇中记录ffmpeg+SDL2播放音频,没加入事件处理。 
    接下来加入事件处理并继续学习音视频同步,再接下来就添加暂停之类的或者添个界面。

    2. 流程图

    这里写图片描述

    3. 示例


    示例代码的主要思想是:(和音频播放器V1.0思想一样,实现不同。不同在于这个程序用一个队列存储主线程读到的AVPacket)

    • 主线程只负责读AVPacket存到队列。—>av_read_frame()
    • 其他所有的解码,输出工作都由callback完成。 
      • callback中从队列中取AVPacketList,再把AVPacketList中的AVPacket拿出来解码。
      • 解码后放到缓冲区,然后输出。
      • 一次调用callback,就输出长度为len的数据(callback第三个参数,一般为2048),不多不少。
      • 当缓冲区没有数据的时候,就去解码,放到缓冲区,再输出。
      • 当解码的数据多于len的时候,就只处理len个,其他的留给后续的callback。
      • callback函数会一次次调用,直到所有处理完。 

    详见代码及代码中注释

    注意:

    • 链表结构是AVPacketList,含两个成员: 
      • AVPacket packet;
      • AVPacketList *next;
    • 代码中注释掉的代码(块注释的),是不用也可以正常运行的,要可能更健壮之类的。

    3.1 代码

    (所有代码都贴了,较长,后面有代码下载地址)

    • audio_player_v2.0.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include <unistd.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
    
    //
    #include "wrap_base.h"
    #include "packet_queue.h"
    
    #define AVCODE_MAX_AUDIO_FRAME_SIZE    192000  //1 second of 48khz 32bit audio
    #define SDL_AUDIO_BUFFER_SIZE   1024    //
    
    #define FILE_NAME               "/home/isshe/Music/WavinFlag.aac"
    #define ERR_STREAM              stderr
    #define OUT_SAMPLE_RATE         44100
    
    AVFrame         wanted_frame;
    PacketQueue     audio_queue;
    int      quit = 0;
    
    void audio_callback(void *userdata, Uint8 *stream, int len);
    int audio_decode_frame(AVCodecContext *pcodec_ctx, uint8_t *audio_buf, int buf_size);
    /*
    void packet_queue_init(PacketQueue *queue);
    int packet_queue_put(PacketQueue *queue, AVPacket *packet);
    int packet_queue_get(PacketQueue *queue, AVPacket *packet, int block);
    */
    int main(int argc, char *argv[])
    {
        AVFormatContext     *pformat_ctx = NULL;
        int                 audio_stream = -1;
        AVCodecContext      *pcodec_ctx = NULL;
        AVCodecContext      *pcodec_ctx_cp = NULL;
        AVCodec             *pcodec = NULL;
        AVPacket            packet ;        //!
        AVFrame             *pframe = NULL;
        char                filename[256] = FILE_NAME;
    
        //SDL
        SDL_AudioSpec       wanted_spec;
        SDL_AudioSpec       spec ;
    
        //ffmpeg 初始化
        av_register_all();
    
        //SDL初始化
        if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER))
        {
            fprintf(ERR_STREAM, "Couldn't init SDL:%s
    ", SDL_GetError());
            exit(-1);
        }
    
        get_file_name(filename, argc, argv);
    
        //打开文件
        if (avformat_open_input(&pformat_ctx, filename, NULL, NULL) != 0)
        {
             fprintf(ERR_STREAM, "Couldn't open input file
    ");
             exit(-1);
        }
    
        //检测文件流信息
        //旧版的是av_find_stream_info()
        if (avformat_find_stream_info(pformat_ctx, NULL) < 0)
        {
             fprintf(ERR_STREAM, "Not Found Stream Info
    ");
             exit(-1);
        }
    
        //显示文件信息,十分好用的一个函数
        av_dump_format(pformat_ctx, 0, filename, false);
    
        //找视音频流
        if (find_stream_index(pformat_ctx, NULL, &audio_stream) == -1)
        {
            fprintf(ERR_STREAM, "Couldn't find stream index
    ");
            exit(-1);
        }
        printf("audio_stream = %d
    ", audio_stream);
    
        //找到对应的解码器
        pcodec_ctx = pformat_ctx->streams[audio_stream]->codec;
        pcodec = avcodec_find_decoder(pcodec_ctx->codec_id);
        if (!pcodec)
        {
             fprintf(ERR_STREAM, "Couldn't find decoder
    ");
             exit(-1);
        }
    
    /*
        pcodec_ctx_cp = avcodec_alloc_context3(pcodec);
        if (avcodec_copy_context(pcodec_ctx_cp, pcodec_ctx) != 0)
        {
            fprintf(ERR_STREAM, "Couldn't copy codec context
    ");
            exit(-1);
        }
     */
    
        //设置音频信息, 用来打开音频设备。
        wanted_spec.freq        = pcodec_ctx->sample_rate;
        wanted_spec.format      = AUDIO_S16SYS;
        wanted_spec.channels    = pcodec_ctx->channels;        //通道数
        wanted_spec.silence     = 0;    //设置静音值
        wanted_spec.samples     = SDL_AUDIO_BUFFER_SIZE;        //读取第一帧后调整?
        wanted_spec.callback    = audio_callback;
        wanted_spec.userdata    = pcodec_ctx;
    
        //wanted_spec:想要打开的
        //spec: 实际打开的,可以不用这个,函数中直接用NULL,下面用到spec的用wanted_spec代替。
        //这里会开一个线程,调用callback。
        //SDL_OpenAudio->open_audio_device(开线程)->SDL_RunAudio->fill(指向callback函数)
        //可以用SDL_OpenAudioDevice()代替
        if (SDL_OpenAudio(&wanted_spec, &spec) < 0)
        {
            fprintf(ERR_STREAM, "Couldn't open Audio:%s
    ", SDL_GetError());
            exit(-1);
        }
    
        //设置参数,供解码时候用, swr_alloc_set_opts的in部分参数
        wanted_frame.format         = AV_SAMPLE_FMT_S16;
        wanted_frame.sample_rate    = spec.freq;
        wanted_frame.channel_layout = av_get_default_channel_layout(spec.channels);
        wanted_frame.channels       = spec.channels;
    
        //初始化AVCondecContext,以及进行一些处理工作。
        avcodec_open2(pcodec_ctx, pcodec, NULL);
    
        //初始化队列
        packet_queue_init(&audio_queue);
    
        //可以用SDL_PauseAudioDevice()代替,目前用的这个应该是旧的。
        //0是不暂停,非零是暂停
        //如果没有这个就放不出声音
        SDL_PauseAudio(0);              //为什么要这个?
    
        //读一帧数据
        while(av_read_frame(pformat_ctx, &packet) >= 0)     //读一个packet,数据放在packet.data
        {
             if (packet.stream_index == audio_stream)
             {
                 packet_queue_put(&audio_queue, &packet);
             }
             else
             {
                 av_free_packet(&packet);
             }
        }
        getchar();      //...
        return 0;
    }
    
    
    //注意userdata是前面的AVCodecContext.
    //len的值常为2048,表示一次发送多少。
    //audio_buf_size:一直为样本缓冲区的大小,wanted_spec.samples.(一般每次解码这么多,文件不同,这个值不同)
    //audio_buf_index: 标记发送到哪里了。
    //这个函数的工作模式是:
    //1. 解码数据放到audio_buf, 大小放audio_buf_size。(audio_buf_size = audio_size;这句设置)
    //2. 调用一次callback只能发送len个字节,而每次取回的解码数据可能比len大,一次发不完。
    //3. 发不完的时候,会len == 0,不继续循环,退出函数,继续调用callback,进行下一次发送。
    //4. 由于上次没发完,这次不取数据,发上次的剩余的,audio_buf_size标记发送到哪里了。
    //5. 注意,callback每次一定要发且仅发len个数据,否则不会退出。
    //如果没发够,缓冲区又没有了,就再取。发够了,就退出,留给下一个发,以此循环。
    //三个变量设置为static就是为了保存上次数据,也可以用全局变量,但是感觉这样更好。
    void audio_callback(void *userdata, Uint8 *stream, int len)
    {
         AVCodecContext *pcodec_ctx     = (AVCodecContext *)userdata;
         int len1 = 0;
         int audio_size = 0;
    
         //注意是static
         //为什么要分那么大?
         static uint8_t         audio_buf[(AVCODE_MAX_AUDIO_FRAME_SIZE * 3) / 2];
         static unsigned int    audio_buf_size = 0;
         static unsigned int    audio_buf_index = 0;
    
         //初始化stream,每次都要。
         SDL_memset(stream, 0, len);
    
         while(len > 0)
         {
              if (audio_buf_index >= audio_buf_size)
              {
                  //数据全部发送,再去获取
                  //自定义的一个函数
                  audio_size = audio_decode_frame(pcodec_ctx, audio_buf, sizeof(audio_buf));
                  if (audio_size < 0)
                  {
                      //错误则静音
                      audio_buf_size = 1024;
                      memset(audio_buf, 0, audio_buf_size);
                  }
                  else
                  {
                      audio_buf_size = audio_size;
                  }
                  audio_buf_index = 0;      //回到缓冲区开头
              }
    
              len1 = audio_buf_size - audio_buf_index;
    //          printf("len1 = %d
    ", len1);
              if (len1 > len)       //len1常比len大,但是一个callback只能发len个
              {
                   len1 = len;
              }
    
              //新程序用 SDL_MixAudioFormat()代替
              //混合音频, 第一个参数dst, 第二个是src,audio_buf_size每次都在变化
              SDL_MixAudio(stream, (uint8_t*)audio_buf + audio_buf_index, len, SDL_MIX_MAXVOLUME);
              //
              //memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
              len -= len1;
              stream += len1;
              audio_buf_index += len1;
         }
    
    }
    
    
    //对于音频来说,一个packet里面,可能含有多帧(frame)数据。
    
    int audio_decode_frame(AVCodecContext *pcodec_ctx,
            uint8_t *audio_buf, int buf_size)
    {
         AVPacket   packet;
         AVFrame    *frame;
         int        got_frame;
         int        pkt_size = 0;
    //     uint8_t    *pkt_data = NULL;
         int        decode_len;
         int        try_again = 0;
         long long  audio_buf_index = 0;
         long long  data_size = 0;
         SwrContext *swr_ctx = NULL;
         int        convert_len = 0;
         int        convert_all = 0;
    
         if (packet_queue_get(&audio_queue, &packet, 1) < 0)
         {
              fprintf(ERR_STREAM, "Get queue packet error
    ");
              return -1;
         }
    
    //     pkt_data = packet.data;
         pkt_size = packet.size;
    //     fprintf(ERR_STREAM, "pkt_size = %d
    ", pkt_size);
    
         frame = av_frame_alloc();
         while(pkt_size > 0)
         {
    //          memset(frame, 0, sizeof(AVFrame));
              //pcodec_ctx:解码器信息
              //frame:输出,存数据到frame
              //got_frame:输出。0代表有frame取了,不意味发生了错误。
              //packet:输入,取数据解码。
              decode_len = avcodec_decode_audio4(pcodec_ctx,
                      frame, &got_frame, &packet);
              if (decode_len < 0) //解码出错
              {
                   //重解, 这里如果一直<0呢?
                   fprintf(ERR_STREAM, "Couldn't decode frame
    ");
                   if (try_again == 0)
                   {
                        try_again++;
                        continue;
                   }
                   try_again = 0;
              }
    
    
              if (got_frame)
              {
    
     /*              //用定的音频参数获取样本缓冲区大小
                   data_size = av_samples_get_buffer_size(NULL,
                           pcodec_ctx->channels, frame->nb_samples,
                           pcodec_ctx->sample_fmt, 1);
    
                   assert(data_size <= buf_size);
    //               memcpy(audio_buf + audio_buf_index, frame->data[0], data_size);
    */
                  //chnanels: 通道数量, 仅用于音频
                  //channel_layout: 通道布局。
                  //多音频通道的流,一个通道布局可以具体描述其配置情况.通道布局这个概念不懂。
                  //大概指的是单声道(mono),立体声道(stereo), 四声道之类的吧?
                  //详见源码及:https://xdsnet.gitbooks.io/other-doc-cn-ffmpeg/content/ffmpeg-doc-cn-07.html#%E9%80%9A%E9%81%93%E5%B8%83%E5%B1%80
    
    
                  if (frame->channels > 0 && frame->channel_layout == 0)
                  {
                       //获取默认布局,默认应该了stereo吧?
                       frame->channel_layout = av_get_default_channel_layout(frame->channels);
                  }
                  else if (frame->channels == 0 && frame->channel_layout > 0)
                  {
                      frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);
                  }
    
    
                  if (swr_ctx != NULL)
                  {
                       swr_free(&swr_ctx);
                       swr_ctx = NULL;
                  }
    
                  //设置common parameters
                  //2,3,4是output参数,4,5,6是input参数。
                  swr_ctx = swr_alloc_set_opts(NULL, wanted_frame.channel_layout,
                          (AVSampleFormat)wanted_frame.format,
                          wanted_frame.sample_rate, frame->channel_layout,
                          (AVSampleFormat)frame->format, frame->sample_rate, 0, NULL);
                  //初始化
                  if (swr_ctx == NULL || swr_init(swr_ctx) < 0)
                  {
                       fprintf(ERR_STREAM, "swr_init error
    ");
                       
  • 相关阅读:
    易错小细节
    Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_GiftAnimationView"
    iOS 获取UIView 动画的实时位置的方法
    控制器跳转后看不到导航栏
    定时器显示礼物弹出的动画,不错的思路
    error===>ld: 2 duplicate symbols for architecture x86_64
    AppStore上传条例
    right here waiting的歌词
    Take my breath away
    Sailing
  • 原文地址:https://www.cnblogs.com/twodog/p/12139485.html
Copyright © 2011-2022 走看看