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
    ");
                       
  • 相关阅读:
    UVA1349 Optimal Bus Route Design 最优巴士路线设计
    POJ3565 Ants 蚂蚁(NEERC 2008)
    UVA1663 Purifying Machine 净化器
    UVa11996 Jewel Magic 魔法珠宝
    NEERC2003 Jurassic Remains 侏罗纪
    UVA11895 Honorary Tickets
    gdb调试coredump(使用篇)
    使用 MegaCLI 检测磁盘状态并更换磁盘
    员工直接坦诚直来直去 真性情
    山东浪潮超越3B4000申泰RM5120-L
  • 原文地址:https://www.cnblogs.com/twodog/p/12139483.html
Copyright © 2011-2022 走看看