zoukankan      html  css  js  c++  java
  • ffplay源码分析04 ---- 音频输出

    =====================================================

    ffplay源码分析01 ---- 框架

    ffplay源码分析02 ---- 数据读取线程

    ffplay源码分析03 ---- 视频解码线程

    ffplay源码分析03 ---- 音频解码线程

    ffplay源码分析04 ---- 音频输出

    ffplay源码分析05 ---- 音频重采样

    ffplay源码分析06 ---- 视频输出

    ffplay源码分析07 ---- 音视频同步

    =====================================================

    简介:

    ffplay的音频输出通过SDL实现,主要流程分为如下几步:

    • 打开音频设备,设置参数
    • 启动SDL音频设备播放
    • SDL音频回调函数读取数据

    代码如下:

    case AVMEDIA_TYPE_AUDIO:
            //从avctx(即AVCodecContext)中获取音频格式参数
            sample_rate    = avctx->sample_rate;
            nb_channels    = avctx->channels;
            channel_layout = avctx->channel_layout;
    
            /* prepare audio output 准备音频输出*/
            //调用audio_open打开sdl音频输出,实际打开的设备参数保存在audio_tgt,返回值表示输出设备的缓冲区大小
            if ((ret = audio_open(is, channel_layout, nb_channels, sample_rate, &is->audio_tgt)) < 0)
                goto fail;
            is->audio_hw_buf_size = ret;
            is->audio_src = is->audio_tgt;  //暂且将数据源参数等同于目标输出参数
            //初始化audio_buf相关参数
            is->audio_buf_size  = 0;
            is->audio_buf_index = 0;
    
            /* init averaging filter 初始化averaging滤镜, 非audio master时使用 */
            is->audio_diff_avg_coef  = exp(log(0.01) / AUDIO_DIFF_AVG_NB);
            is->audio_diff_avg_count = 0;
            /* 由于我们没有精确的音频数据填充FIFO,故只有在大于该阈值时才进行校正音频同步*/
            is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec;
    
            is->audio_stream = stream_index;    // 获取audio的stream索引
            is->audio_st = ic->streams[stream_index];  // 获取audio的stream指针
            // 初始化ffplay封装的音频解码器
            decoder_init(&is->auddec, avctx, &is->audioq, is->continue_read_thread);
            if ((is->ic->iformat->flags & (AVFMT_NOBINSEARCH | AVFMT_NOGENSEARCH | AVFMT_NO_BYTE_SEEK)) && !is->ic->iformat->read_seek) {
                is->auddec.start_pts = is->audio_st->start_time;
                is->auddec.start_pts_tb = is->audio_st->time_base;
            }
            // 启动音频解码线程
            if ((ret = decoder_start(&is->auddec, audio_thread, "audio_decoder", is)) < 0)
                goto out;
            // 0:播放 非0:暂停
            SDL_PauseAudioDevice(audio_dev, 0);
            break;

    打开音频设备

    static int audio_open(void *opaque, int64_t wanted_channel_layout,
                          int wanted_nb_channels, int wanted_sample_rate,
                          struct AudioParams *audio_hw_params)
    {
        SDL_AudioSpec wanted_spec, spec;
        const char *env;
        static const int next_nb_channels[] = {0, 0, 1, 6, 2, 6, 4, 6};
        static const int next_sample_rates[] = {0, 44100, 48000, 96000, 192000};
        int next_sample_rate_idx = FF_ARRAY_ELEMS(next_sample_rates) - 1;
    
        env = SDL_getenv("SDL_AUDIO_CHANNELS");
        if (env) {  // 若环境变量有设置,优先从环境变量取得声道数和声道布局
            wanted_nb_channels = atoi(env);
            wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
        }
        if (!wanted_channel_layout || wanted_nb_channels != av_get_channel_layout_nb_channels(wanted_channel_layout)) {
            wanted_channel_layout = av_get_default_channel_layout(wanted_nb_channels);
            wanted_channel_layout &= ~AV_CH_LAYOUT_STEREO_DOWNMIX;
        }
        // 根据channel_layout获取nb_channels,当传入参数wanted_nb_channels不匹配时,此处会作修正
        wanted_nb_channels = av_get_channel_layout_nb_channels(wanted_channel_layout);
        wanted_spec.channels = wanted_nb_channels;
        wanted_spec.freq = wanted_sample_rate;
        if (wanted_spec.freq <= 0 || wanted_spec.channels <= 0) {
            av_log(NULL, AV_LOG_ERROR, "Invalid sample rate or channel count!
    ");
            return -1;
        }
        while (next_sample_rate_idx && next_sample_rates[next_sample_rate_idx] >= wanted_spec.freq)
            next_sample_rate_idx--;  // 从采样率数组中找到第一个不大于传入参数wanted_sample_rate的值
        // 音频采样格式有两大类型:planar和packed,假设一个双声道音频文件,一个左声道采样点记作L,一个右声道采样点记作R,则:
        // planar存储格式:(plane1)LLLLLLLL...LLLL (plane2)RRRRRRRR...RRRR
        // packed存储格式:(plane1)LRLRLRLR...........................LRLR
        // 在这两种采样类型下,又细分多种采样格式,如AV_SAMPLE_FMT_S16、AV_SAMPLE_FMT_S16P等,
        // 注意SDL2.0目前不支持planar格式
        // channel_layout是int64_t类型,表示音频声道布局,每bit代表一个特定的声道,参考channel_layout.h中的定义,一目了然
        // 数据量(bits/秒) = 采样率(Hz) * 采样深度(bit) * 声道数
        wanted_spec.format = AUDIO_S16SYS;
        wanted_spec.silence = 0;
        /*
         * 一次读取多长的数据
         * SDL_AUDIO_MAX_CALLBACKS_PER_SEC一秒最多回调次数,避免频繁的回调
         *  Audio buffer size in samples (power of 2)
         */
         wanted_spec.samples = FFMAX(SDL_AUDIO_MIN_BUFFER_SIZE,
                                    2 << av_log2(wanted_spec.freq / SDL_AUDIO_MAX_CALLBACKS_PER_SEC));
        wanted_spec.callback = sdl_audio_callback;
        wanted_spec.userdata = opaque;
        // 打开音频设备并创建音频处理线程。期望的参数是wanted_spec,实际得到的硬件参数是spec
        // 1) SDL提供两种使音频设备取得音频数据方法:
        //    a. push,SDL以特定的频率调用回调函数,在回调函数中取得音频数据
        //    b. pull,用户程序以特定的频率调用SDL_QueueAudio(),向音频设备提供数据。此种情况wanted_spec.callback=NULL
        // 2) 音频设备打开后播放静音,不启动回调,调用SDL_PauseAudio(0)后启动回调,开始正常播放音频
        // SDL_OpenAudioDevice()第一个参数为NULL时,等价于SDL_OpenAudio()
        while (!(audio_dev = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE))) {
            av_log(NULL, AV_LOG_WARNING, "SDL_OpenAudio (%d channels, %d Hz): %s
    ",
                   wanted_spec.channels, wanted_spec.freq, SDL_GetError());
            wanted_spec.channels = next_nb_channels[FFMIN(7, wanted_spec.channels)];
            if (!wanted_spec.channels) {
                wanted_spec.freq = next_sample_rates[next_sample_rate_idx--];
                wanted_spec.channels = wanted_nb_channels;
                if (!wanted_spec.freq) {
                    av_log(NULL, AV_LOG_ERROR,
                           "No more combinations to try, audio open failed
    ");
                    return -1;
                }
            }
            wanted_channel_layout = av_get_default_channel_layout(wanted_spec.channels);
        }
        // 检查打开音频设备的实际参数:采样格式
        if (spec.format != AUDIO_S16SYS) {
            av_log(NULL, AV_LOG_ERROR,
                   "SDL advised audio format %d is not supported!
    ", spec.format);
            return -1;
        }
        // 检查打开音频设备的实际参数:声道数
        if (spec.channels != wanted_spec.channels) {
            wanted_channel_layout = av_get_default_channel_layout(spec.channels);
            if (!wanted_channel_layout) {
                av_log(NULL, AV_LOG_ERROR,
                       "SDL advised channel count %d is not supported!
    ", spec.channels);
                return -1;
            }
        }
        // wanted_spec是期望的参数,spec是实际的参数,wanted_spec和spec都是SDL中的结构。
        // 此处audio_hw_params是FFmpeg中的参数,输出参数供上级函数使用
        // audio_hw_params保存的参数,就是在做重采样的时候要转成的格式。
        audio_hw_params->fmt = AV_SAMPLE_FMT_S16;
        audio_hw_params->freq = spec.freq;
        audio_hw_params->channel_layout = wanted_channel_layout;
        audio_hw_params->channels =  spec.channels;
        /* audio_hw_params->frame_size这里只是计算一个采样点占用的字节数 */
        audio_hw_params->frame_size = av_samples_get_buffer_size(NULL, audio_hw_params->channels,
                                                                 1, audio_hw_params->fmt, 1);
        audio_hw_params->bytes_per_sec = av_samples_get_buffer_size(NULL, audio_hw_params->channels,
                                                                    audio_hw_params->freq,
                                                                    audio_hw_params->fmt, 1);
        if (audio_hw_params->bytes_per_sec <= 0 || audio_hw_params->frame_size <= 0) {
            av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size failed
    ");
            return -1;
        }
        // 比如2帧数据,一帧就是1024个采样点, 1024*2*2 * 2 = 8192字节
        return spec.size;    /* SDL内部缓存的数据字节, samples * channels *byte_per_sample */
    }

    读取数据

    static void sdl_audio_callback(void *opaque, Uint8 *stream, int len)
    {
        VideoState *is = opaque;
        int audio_size, len1;
    
        audio_callback_time = av_gettime_relative();
    
        while (len > 0) {   // 循环读取,直到读取到足够的数据
            /* (1)如果is->audio_buf_index < is->audio_buf_size则说明上次拷贝还剩余一些数据,
             * 先拷贝到stream再调用audio_decode_frame
             * (2)如果audio_buf消耗完了,则调用audio_decode_frame重新填充audio_buf
             */
            if (is->audio_buf_index >= is->audio_buf_size) {
                audio_size = audio_decode_frame(is);
                if (audio_size < 0) {
                    /* if error, just output silence */
                    is->audio_buf = NULL;
                    is->audio_buf_size = SDL_AUDIO_MIN_BUFFER_SIZE / is->audio_tgt.frame_size
                            * is->audio_tgt.frame_size;
                } else {
                    if (is->show_mode != SHOW_MODE_VIDEO)
                        update_sample_display(is, (int16_t *)is->audio_buf, audio_size);
                    is->audio_buf_size = audio_size; // 讲字节 多少字节
                }
                is->audio_buf_index = 0;
            }
            //根据缓冲区剩余大小量力而行
            len1 = is->audio_buf_size - is->audio_buf_index;
            if (len1 > len)  // len = 3000 < len1 4096
                len1 = len;
            //根据audio_volume决定如何输出audio_buf
            /* 判断是否为静音,以及当前音量的大小,如果音量为最大则直接拷贝数据 */
            if (!is->muted && is->audio_buf && is->audio_volume == SDL_MIX_MAXVOLUME)
                memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
            else {
                memset(stream, 0, len1);
                // 3.调整音量
                /* 如果处于mute状态则直接使用stream填0数据, 暂停时is->audio_buf = NULL */
                if (!is->muted && is->audio_buf)
                    SDL_MixAudioFormat(stream, (uint8_t *)is->audio_buf + is->audio_buf_index,
                                       AUDIO_S16SYS, len1, is->audio_volume);
            }
            len -= len1;
            stream += len1;
            /* 更新is->audio_buf_index,指向audio_buf中未被拷贝到stream的数据(剩余数据)的起始位置 */
            is->audio_buf_index += len1;
        }
        is->audio_write_buf_size = is->audio_buf_size - is->audio_buf_index;
        /* Let's assume the audio driver that is used by SDL has two periods. */
        if (!isnan(is->audio_clock)) {
            set_clock_at(&is->audclk, is->audio_clock -
                         (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)
                         / is->audio_tgt.bytes_per_sec,
                         is->audio_clock_serial,
                         audio_callback_time / 1000000.0);
            sync_clock_to_slave(&is->extclk, &is->audclk);
        }
    }
  • 相关阅读:
    进入用友通:提示"由于文件不可访问,内存磁盘空间不足无法打开ufsystem数据库"...
    HDOJ 1069 Monkey and Banana
    HDOJ 1087 Super Jumping! Jumping! Jumping!
    HDOJ 1209 Clock
    CodeForces Round #185 (Div. 2)A,B,C
    HDOJ 1465 不容易系列之一
    HDOJ 1114 PiggyBank
    HDOJ 1280 前m大的数
    HDOJ 1495 非常可乐
    HDOJ 1284 钱币兑换问题
  • 原文地址:https://www.cnblogs.com/vczf/p/14142052.html
Copyright © 2011-2022 走看看