精华内容
下载资源
问答
  • ffmpeg音频采集、编码

    2021-09-08 17:20:25
    音频采集 在windows上获取音频的方法,主要尝试了2种,效果差不多。一种是通过ffmpeg的dshow获取,另一种是直接从windows的Core Audio API 来获取。通过这两种方式采集到的音频的采样率都是当前声音播放的扬声器的...

    音频采集

    在windows上获取音频的方法,主要尝试了2种,效果差不多。一种是通过ffmpeg的dshow获取,另一种是直接从windows的Core Audio API 来获取。通过这两种方式采集到的音频的采样率都是当前声音播放的扬声器的采样率。

    ffmpeg dshow 采集

    使用dshow抓屏需要安装抓屏软件:screen-capture-recorder

    在命令行用dshow:

    ffmpeg -f dshow -i video="screen-capture-recorder" -f dshow -i audio="virtual-audio-capturer" -t 30 -r 20 -vcodec libx264 output.mp4

    在代码中用dshow: 

    //视频采集
    AVInputFormat *ifmt=av_find_input_format("dshow");
     if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){
      qDebug() << "Couldn't open input stream.";
      return -1;
     }
    
    //音频采集
    AVInputFormat *ifmt=av_find_input_format("dshow");
     if(avformat_open_input(&pFormatCtx,audio="virtual-audio-capturer",ifmt,NULL)!=0){
      qDebug() << "Couldn't open input stream.";
      return -1;
     }

    具体采集可以参考ffmpeg实现录屏+录音,懒得贴代码了。

    Core Audio 音频采集

    Core Audio不支持XP,只可以在Vista以上(包括Vista)的操作系统中才能使用,主要用来取代Wave系列API函数和DirectSound。

    具体采集可以参考windows音频声卡采集。主要说下采集到的buffer中framesAvailable就是采集一次包含的采样点多少了,同AVFrame中的nb_samples:

    hr = m_CACaptureClient->GetBuffer(&pData, &framesAvailable, &flags, NULL, NULL);
            if (SUCCEEDED(hr))
            {
                if (framesAvailable!=0)
                {
                    if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
                    {
                        pData = NULL;
                    }
                    else
                    {
                        //Copy data from the audio engine buffer to the output buffer.
                        int nDataLen = framesAvailable*m_CAFrameSize;
    
                        AVFrame *frame;
                        frame = av_frame_alloc();
                        frame->format = m_SrcParams.sample_fmt;
                        frame->nb_samples = framesAvailable;
                        frame->channels = m_SrcParams.channels;
                        frame->channel_layout = av_get_default_channel_layout(m_SrcParams.channels);
                        frame->sample_rate = m_SrcParams.sample_rate;
    
                        av_frame_get_buffer(frame, 1);
                        memcpy(frame->data[0], pData, nDataLen);
    
                        EncoderData *node = new EncoderData();
                        node->type = DATA_TYPE_AUDIO;
                        node->frame = frame;
    
    //                    fwrite(pData, 1, nDataLen, pcmFp);
    
                        async_queue_push(m_AvailPtr, (void *)node);
    
                        CalcCapRate();
                    }
                }
            }

     

    音频格式中的Plane

    默认用ffmpeg采集到的格式是AV_SAMPLE_FMT_S16,但是AAC编码要的又是AV_SAMPLE_FMT_FLTP,中间需要通过swr_convert来转换。

    enum AVSampleFormat {
        AV_SAMPLE_FMT_NONE = -1,
        AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
        AV_SAMPLE_FMT_S16,         ///< signed 16 bits
        AV_SAMPLE_FMT_S32,         ///< signed 32 bits
        AV_SAMPLE_FMT_FLT,         ///< float
        AV_SAMPLE_FMT_DBL,         ///< double
    
        AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
        AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
        AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
        AV_SAMPLE_FMT_FLTP,        ///< float, planar
        AV_SAMPLE_FMT_DBLP,        ///< double, planar
        AV_SAMPLE_FMT_S64,         ///< signed 64 bits
        AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar
    
        AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
    };

    1. 无论是不是带P的数据总量是相同的.
    2. 带P的格式是左右声道分开存储的:
      data[0]:LLLLLLLLLLLLL....; data[1]: RRRRRRRRR...
    3. 不带P的格式是交替存储的,只在data[0] 有数据:
      data[0] :LRLRLRLRLRLRLRLRLRLR....

    AAC编码

    AAC帧时长

    一个AAC原始帧包含某段时间内1024个采样点相关数据(mp3则包含1152个采样点)。音频帧的播放时间 = 一个AAC帧对应的采样样本的个数 / 采样频率(单位为s)。

    1. 采样率(sample rate)为 44100Hz,表示每秒 44100个采样点, 根据公式,当前一帧的播放时间 = 1024 * 1000/44100= 23.22ms(单位为ms)

    2. 采样率为48000Hz,根据公式,当前一帧的播放时间 = 1024 * 1000/48000= 21.33ms(单位为ms)

    AAC帧数据大小

    AV_SAMPLE_FMT_FLTP 格式 (32bits per sample)、AV_SAMPLE_FMT_S16 格式 (16bits per sample),采集到的PCM是AV_SAMPLE_FMT_S16 格式

    AAC帧一帧包含1024个采样点,数据量大小 = 一个AAC帧对应的采样样本的个数 * 通道数* 数据位数/8。采集数据为双通道,16位数据时,根据公式, 一帧数据量大小 = 1024*2 *16/8 = 4096。

    在采集采样率和编码采样率相同的情况下(就是不需要重采样),通道数,数据位数一般不会改变,那么采集的数据大小和编码的数据大小是一样的。也就是说,当采集了4096个字节的数据后,再送去给编码器编码一帧AAC帧,不同的采样率只是会改变每秒钟的AAC帧的数量。

    1. 采样率(sample rate)为 44100Hz,1秒钟有:44100 / 1024 = 43.066帧

    2. 采样率为48000Hz,1秒钟有:48000 / 1024 = 48000 / 1024 = 46.875帧

    所以,如果自己弄一个音频缓冲buffer,就需要每满4096字节就编码一帧,更简单的方式是用ffmpeg提供的AVAudioFifo,每次读取写入都是根据采样点的个数来的:

        av_audio_fifo_write(m_pAudioFifo, (void **)frame->data, frame->nb_samples);
    
        av_frame_free(&frame);
        RELEASE_CLASS(node);
    
        while(av_audio_fifo_size(m_pAudioFifo) >= m_inputSamples)
        {
            av_audio_fifo_read(m_pAudioFifo, (void **)m_pInputData, m_inputSamples);
            ...
        }

    AAC的ADTS

    ADTS 头中相对有用的信息 采样率、声道数、帧长度。想想也是,我要是解码器的话,你给我一堆得AAC音频ES流我也解不出来。每一个带ADTS头信息的AAC流会清晰的告送解码器他需要的这些信息。

    这里有个坑爹的情况,ffmpeg的2.5版本,编码出的AAC帧好像是带有ADTS的,但是最新的ffmpeg4.0没有ADTS,直接把编码的AAC帧扔给播放器是没有声音的,需要你自己加...想要了解ADTS相关信息的可以看看这里,我就贴下从gayhub上翻到的添加ADTS的相关代码:

    #define ADTS_HEADER_SIZE 7
    
    const int avpriv_mpeg4audio_sample_rates[16] = {
        96000, 88200, 64000, 48000, 44100, 32000,
        24000, 22050, 16000, 12000, 11025, 8000, 7350
    };
    
    const uint8_t ff_mpeg4audio_channels[8] = {
        0, 1, 2, 3, 4, 5, 6, 8
    };
    
    static int GetSampleIndex(int sample_rate)
    {
        for (int i = 0; i < 16; i++)
        {
            if (sample_rate == avpriv_mpeg4audio_sample_rates[i])
            {
                return i;
            }
        }
        return -1;
    }
    
    void AudioEncoder::WriteADTSHeader(int Size, int sample_rate,int channels)
    {
        if (ADTSHeader == nullptr)
        {
            ADTSHeader = (char*)av_malloc(ADTS_HEADER_SIZE);
        }
        memset(ADTSHeader,0, ADTS_HEADER_SIZE);
    
        int length = ADTS_HEADER_SIZE + Size;
        length &= 0x1FFF;
    
        int sample_index = GetSampleIndex(sample_rate);
        int channel = 0;
    
        if (channels < (int)FF_ARRAY_ELEMS(ff_mpeg4audio_channels))
            channel = ff_mpeg4audio_channels[channels];
    
        ADTSHeader[0] = (char)0xff;
        ADTSHeader[1] = (char)0xf1;
        ADTSHeader[2] = (char)(0x40 | (sample_index << 2) | (channel >> 2));
        ADTSHeader[3] = (char)((channel & 0x3) << 6 | (length >> 11));
        ADTSHeader[4] = (char)(length >> 3) & 0xff;
        ADTSHeader[5] = (char)(((length & 0x7) << 5) & 0xff) | 0x1f;
        ADTSHeader[6] = (char)0xfc;
    }
    
    int AudioEncoder::ADTS(AVPacket *src, AVPacket **des)
    {
        if (src == nullptr) {
            return -1;
        }
        if (des == nullptr) {
            return -1;
        }
    
        AVPacket *adtsPacket = av_packet_alloc();
        av_init_packet(adtsPacket);
        av_new_packet(adtsPacket, src->size + ADTS_HEADER_SIZE);
        WriteADTSHeader(src->size, m_pCoderCtx->sample_rate, m_pCoderCtx->channels);
        memcpy(adtsPacket->data, ADTSHeader, ADTS_HEADER_SIZE);
        memcpy(adtsPacket->data + ADTS_HEADER_SIZE, src->data, src->size);
    
        adtsPacket->pts = src->pts;
        adtsPacket->dts = src->dts;
        adtsPacket->duration = src->duration;
        adtsPacket->flags = src->flags;
        adtsPacket->stream_index = src->stream_index;
        adtsPacket->pos = src->pos;
    
        if (*des == src)
        {
            av_packet_unref(src);
            av_packet_move_ref(*des, adtsPacket);
        }
        else if (*des != nullptr)
        {
            av_packet_move_ref(*des, adtsPacket);
        }
        else
        {
            *des = adtsPacket;
        }
    
        return 0;
    }

    使用方法就是在初始化的时候调用下AudioEncoder::WriteADTSHeader;编码出来一个AAC帧的packet就调用下AudioEncoder::ADTS(packet, *packet); 播放器就可以愉快的播放啦~~

    重采样

    先说下我碰到的问题,我的需求是将声卡采集后传输到另一个设备上播放,用swr_convert重采样的时候碰到个坑,我设置声卡的采样率为44100Hz的时候,如果编码采样率设置为48000Hz,每次要是扔给编码器4096的数据的话,编码出来的AAC帧拿去播放时间会变长,实时采集再播放的时候声音就会越来越慢,但是我也没有什么buffer存着数据,后来发现:

    /** Convert audio.
     *
     * in and in_count can be set to 0 to flush the last few samples out at the
     * end.
     *
     * If more input is provided than output space, then the input will be buffered.
     * You can avoid this buffering by using swr_get_out_samples() to retrieve an
     * upper bound on the required number of output samples for the given number of
     * input samples. Conversion will run directly without copying whenever possible.
     *
     * @param s         allocated Swr context, with parameters set
     * @param out       output buffers, only the first one need be set in case of packed audio
     * @param out_count amount of space available for output in samples per channel
     * @param in        input buffers, only the first one need to be set in case of packed audio
     * @param in_count  number of input samples available in one channel
     *
     * @return number of samples output per channel, negative value on error
     */
    int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,
                                    const uint8_t **in , int in_count);

    注释说到,如果提供的输入多于输出空间,则输入将被缓冲...应该是缓冲在这里了Orz...打印了下swrcontext的buffer:

    int fifo_size = swr_get_out_samples(m_pSwrCtx, 0);
    qDebug() << "swr_get_out_samples"  << fifo_size;

    确实在涨....emmm

    看了下其他博客的重采样,感觉是解码了AAC帧再重新采样编码,我的情况是想采集了(采集的帧只有448个采样点) 直接重采样编码,不想编码完了再重采样一遍。

    回到上面关于数据大小的讨论,在采集44100->编码44100、采集48000->编码48000的情况下,采集和编码的数据量是相同的,在用swr_convert的时候,in_count、out_count相等就行:

    swr_convert(m_pSwrCtx, m_pDesFrame->data, m_pCoderCtx->frame_size, (const uint8_t**)m_pConvertData, m_pCoderCtx->frame_size);

    那么,在44100->48000的情况下,out_count由于AAC帧需要,1024不变,设in_count为x,得到个等式:

    44100/x = 48000 /1024  =>  x = 44100*1024/48000 =>  x = 940.8

    差不多就取个941,由于不是整数,out_count也要设置比1024大点:

    swr_convert(m_pSwrCtx, m_pDesFrame->data, 1040, (const uint8_t**)m_pInputData, 941);

    swr的buffer差不多保持在38左右,不是很明白为啥是这个值,但是需求基本达到了.

    不过反过来就不行了,48000->44100要是把in_count直接改成1114的话,程序会崩溃(:з)∠),如果有需要的话可以采集完了先重采样,然后再扔去编码,同时操作高采样到低采样有点问题。

    参考链接

    展开全文
  • ffmpeg 音频数据采集

    2017-06-22 09:56:22
    ffmpeg 音频数据采集 开发环境vs2010 学习从零开始学习音视频编程技术(十七) 录屏软件开发之音频采集 源码
  • 利用ffmpeg查看采集设备 ffmpeg -hide_banner -list_devices true -f dshow -i dummy ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition Audio)" -codec:a aac -ac 2 -ar 44100 -f flv "rtmp://...

    本地搭建直播流媒体服务

    LiveQing直播点播流媒体服务下载

    利用ffmpeg查看采集设备

    ffmpeg  -hide_banner -list_devices true -f dshow -i dummy
    

    利用设备管理查看采集设备

    在这里插入图片描述

    音频采集推流

    ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition Audio)" -codec:a aac -ac 2 -ar 44100 -f flv "rtmp://127.0.0.1:10085/live/test"
    

    在这里插入图片描述

    LiveQing中的开放直播查看

    在这里插入图片描述

    更多视频流媒体解决方案

    青柿流媒体服务解决方案

    支持本地内网私有云部署;为企业视频能力建设,提供了视频点播转码、手机直播推流、云端录像存储计划、RTMP拉流推流服务、RTSP拉流推流服务、设备GB28181接入Onvif云台控制等等能力,同时提供性能强大稳定的RTMP/HLS/RTSP/HTTP-FLV分发,支持H5页面无插件直播,强大的后台管理,详细的二次开发接口文档,服务搭建简单解压后一键启动,支持WindowsLinux环境部署

    展开全文
  • 本地搭建直播媒体服务 LiveQing直播点播媒体服务下载 利用ffmpeg查看采集设备 ffmpeg -hide_banner -list_...音频采集 ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition Audio)" -codec:a...

    本地搭建直播流媒体服务

    LiveQing直播点播流媒体服务下载

    利用ffmpeg查看采集设备

    ffmpeg  -hide_banner -list_devices true -f dshow -i dummy
    

    利用设备管理查看采集设备

    在这里插入图片描述

    音频采集推流

    ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition Audio)" -codec:a aac -ac 2 -ar 44100 -f flv "rtmp://127.0.0.1:10085/live/test"
    

    在这里插入图片描述

    LiveQing中的开放直播查看

    在这里插入图片描述

    更多视频流媒体解决方案

    青柿流媒体服务解决方案

    支持本地内网私有云部署;为企业视频能力建设,提供了视频点播转码、手机直播推流、云端录像存储计划、RTMP拉流推流服务、RTSP拉流推流服务、设备GB28181接入Onvif云台控制等等能力,同时提供性能强大稳定的RTMP/HLS/RTSP/HTTP-FLV分发,支持H5页面无插件直播,强大的后台管理,详细的二次开发接口文档,服务搭建简单解压后一键启动,支持WindowsLinux环境部署

    WEB: https://www.liveqing.com/

    展开全文
  • 音频采集 via FFmpeg

    千次阅读 2019-01-29 13:56:11
    音频采集 via FFmpegFFmpeg 命令行采集音频FFmpeg API 采集音频音频重采样FFmpeg 采集音频代码概览open_cap_device 函数enum_dshow_acap_devices 函数open_output_audio_file 函数open_output_file 函数init_audio_...

    FFmpeg 简介

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用 LGPL 或 GPL 许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频 / 视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 code 都是从头开发的。

    FFmpeg 在 Linux 平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括 Windows、Mac OS X 等。这个项目最早由 Fabrice Bellard 发起,2004 年至 2015 年间由 Michael Niedermayer 主要负责维护。许多 FFmpeg 的开发人员都来自 MPlayer 项目,而且当前 FFmpeg 也是放在 MPlayer 项目组的服务器上。项目的名称来自 MPEG 视频编码标准,前面的 “FF” 代表 “Fast Forward”。

    FFmpeg 命令行采集音频

    FFmpeg 提供了现成的程序用命令行的方式对音频进行采集。

    • 首先需要枚举电脑上的音频采集设备:
    >ffmpeg.exe -list_devices true -f dshow -i dummy
    … …
    [dshow @ 007bd020] DirectShow video devices (some may be both video and audio devices)
    [dshow @ 007bd020]  “USB Web Camera - HD"
    [dshow @ 007bd020]     Alternative name "@device_pnp_\\?\usb#vid_1bcf&pid_288e&mi_00#7&6c75a67&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
    [dshow @ 003cd000] DirectShow audio devices
    [dshow @ 003cd000]  "Microphone (Realtek High Defini"
    [dshow @ 003cd000]     Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\Microphone (Realtek High Defini“
    

    在我的电脑上有两个采集设备,一个是用来采集视频的摄像头,一个是用来采集音频的麦克风“Microphone (Realtek High Defini ”(此处名称因为太长被截断)。

    • 然后选择麦克风设备进行采集
    >ffmpeg.exe -f dshow -i audio="Microphone (Realtek High Defini" d:\test.mp3 
    ... ...
    Guessed Channel Layout for Input Stream #0.0 : stereo
    Input #0, dshow, from 'audio=Microphone (Realtek High Defini':
      Duration: N/A, start: 38604.081000, bitrate: 1411 kb/s
        Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
    Stream mapping:
      Stream #0:0 -> #0:0 (pcm_s16le (native) -> mp3 (libmp3lame))
    Press [q] to stop, [?] for help
    Output #0, mp3, to 'd:\test.mp3':
      Metadata:
        TSSE            : Lavf57.71.100
        Stream #0:0: Audio: mp3 (libmp3lame), 44100 Hz, stereo, s16p
        Metadata:
          encoder       : Lavc57.89.100 libmp3lame
    size=102kB time=00:00:06.48 bitrate=128.7kbits/s speed=2.13x ← 正在录制的音频信息,会实时变化
    

    FFmpeg 在 Windows 上用的是 DirectShow 输入设备进行采集的,然后经由自己的 mp3 encoder 和 file writer 写到磁盘上。

    FFmpeg API 采集音频

    FFmpeg 还提供了完备的 API 对音频进行采集。

    下面是使用 FFmpeg 采集并编码音频的流程图。使用该流程,可以编码 MP3、AAC、FLAC 等等各种 FFmpeg 支持的音频。
    FFmpeg 采集音频

    音频重采样

    音频重采样 是转换已采样的音频数据的过程,比如当输入输出数据的采样率不一致时,或者声道数不一致时,就需要重采样。音频重采样主要步骤是进行抽取或插值。由于抽取可能产生混叠,插值可能产生镜像,因此需要在抽取前进行抗混叠滤波,在插值后进行抗镜像滤波。抗混叠滤波和抗镜像滤波都是使用低通滤波器实现。

    FFmpeg 提供了重采样的 API,主要流程如下:

    swr_alloc_set_opts
    swr_init
    swr_convert
    swr_free

    采集音频代码

    以下是整个 FFmpeg 采集过程的概要代码,略去各个函数的具体实现和资源释放。

    本文中的代码基于 FFmpeg 4.1。

    hr = open_cap_device(AVMEDIA_TYPE_AUDIO, &cap_fmt_ctx, &cap_codec_ctx);
    GOTO_IF_FAILED(hr);
    
    hr = open_output_audio_file(out_file, cap_codec_ctx, &out_fmt_ctx, &enc_ctx);
    GOTO_IF_FAILED(hr);
    
    hr = init_resampler(cap_codec_ctx, enc_ctx, &resample_ctx);
    GOTO_IF_FAILED(hr);
    
    hr = init_fifo(&fifo, enc_ctx);
    GOTO_IF_FAILED(hr);
    
    hr = avformat_write_header(out_fmt_ctx, NULL);
    GOTO_IF_FAILED(hr);
    
    while (_kbhit() == 0) { // Infinitely capture audio until a key input.
        int finished = 0;
        hr = audio_transcode( cap_fmt_ctx, cap_codec_ctx, out_fmt_ctx, enc_ctx,
                	      fifo, resample_ctx, 0, &finished, false, true );
        GOTO_IF_FAILED(hr);
        if (finished)
            break;
    }
    
    flush_encoder(out_fmt_ctx, enc_ctx);
    
    hr = av_write_trailer(out_fmt_ctx);
    GOTO_IF_FAILED(hr);
    

    open_cap_device 函数

    在 Windows 上 FFmpeg 使用 DirectShow 的设备进行采集,这里我们先枚举所有的设备,然后选用第一个成功初始化的设备。

    int open_cap_device(
        AVMediaType cap_type,
        AVFormatContext **cap_fmt_ctx, 
        AVCodecContext **cap_codec_ctx,
        AVDictionary** options = NULL)
    {
        RETURN_IF_NULL(cap_fmt_ctx);
        RETURN_IF_NULL(cap_codec_ctx);
        *cap_fmt_ctx = NULL;
        *cap_codec_ctx = NULL;
        int hr = -1;
        std::string cap_device_name;
        std::vector<std::wstring> cap_devices;
    
        avdevice_register_all();
        CoInitialize(NULL);
    
        AVInputFormat* input_fmt = av_find_input_format("dshow");
        GOTO_IF_NULL(input_fmt);
    
        switch (cap_type) {
        case AVMEDIA_TYPE_AUDIO:
            cap_device_name = "audio=";
            hr = enum_dshow_acap_devices(cap_devices);
            break;
        }
        GOTO_IF_FAILED(hr);
    
        cap_device_name += unicodeToUtf8(cap_devices[0].c_str());
        hr = avformat_open_input(cap_fmt_ctx, cap_device_name.c_str(), input_fmt, options);
        GOTO_IF_FAILED(hr);
    
        hr = avformat_find_stream_info(*cap_fmt_ctx, NULL);
        GOTO_IF_FAILED(hr);
    
        for (unsigned int i = 0; i < (*cap_fmt_ctx)->nb_streams; i++) {
            AVCodecParameters* codec_par = (*cap_fmt_ctx)->streams[i]->codecpar;
            if (codec_par->codec_type == cap_type) {
                av_dump_format(*cap_fmt_ctx, i, NULL, 0);
    
                AVCodec* decoder = avcodec_find_decoder(codec_par->codec_id);
                GOTO_IF_NULL(decoder);
    
                *cap_codec_ctx = avcodec_alloc_context3(decoder);
                GOTO_IF_NULL(*cap_codec_ctx);
    
                /** initialize the stream parameters with demuxer information */
                hr = avcodec_parameters_to_context(*cap_codec_ctx, codec_par);
                GOTO_LABEL_IF_FAILED(hr, OnErr);
    
                hr = avcodec_open2(*cap_codec_ctx, decoder, NULL);
                GOTO_IF_FAILED(hr);
    
                break;
            }
        }
        GOTO_IF_NULL(*cap_codec_ctx);
    
        hr = 0;
    RESOURCE_FREE:
        CoUninitialize();
        return hr;
    
    OnErr:
        avformat_free_context(*cap_fmt_ctx);
        *cap_fmt_ctx = NULL;
    
        if (NULL != *cap_codec_ctx)
            avcodec_free_context(cap_codec_ctx);
    
        goto RESOURCE_FREE;
    }
    

    enum_dshow_acap_devices 函数

    下面是使用 DShow 的 API 对音频采集设备进行枚举。

    HRESULT enum_dshow_acap_devices(std::vector<std::wstring>& devices)
    {
        return enum_dshow_devices(CLSID_AudioInputDeviceCategory, devices);
    }
    
    HRESULT enum_dshow_devices(const IID& deviceCategory, std::vector<std::wstring>& devices)
    {
        HRESULT hr = E_FAIL;
        CComPtr <ICreateDevEnum> pDevEnum =NULL;
        CComPtr <IEnumMoniker> pClassEnum = NULL;
        CComPtr<IMoniker> pMoniker =NULL;
        ULONG cFetched = 0;
    
        hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void**)&pDevEnum);
        RETURN_IF_FAILED(hr);
    
        hr = pDevEnum->CreateClassEnumerator(deviceCategory, &pClassEnum, 0);
        RETURN_IF_FAILED(hr);
        // If there are no enumerators for the requested type, then
        // CreateClassEnumerator will succeed, but pClassEnum will be NULL.
        RETURN_IF_NULL(pClassEnum);
    
        while (S_OK == (pClassEnum->Next(1, &pMoniker, &cFetched))) {
            CComPtr<IPropertyBag> pPropertyBag = NULL;
            hr = pMoniker->BindToStorage(NULL, NULL, IID_IPropertyBag, (void**)&pPropertyBag);
            pMoniker = NULL;
            if (FAILED(hr))
                continue;
    
            CComVariant friendlyName;
            friendlyName.vt = VT_BSTR;
            hr = pPropertyBag->Read(L"FriendlyName", &friendlyName, NULL) ;
            if (SUCCEEDED(hr)) {
                std::wstring strFriendlyName(friendlyName.bstrVal);
                devices.push_back(strFriendlyName);
            }
        }
        RETURN_IF_TRUE(devices.empty(), E_FAIL);
    
        return S_OK;
    }
    

    open_output_audio_file 函数

    打开一个输出文件并初始化音频编码器。

    int open_output_audio_file(
        const char *file_name,
        AVCodecContext *dec_ctx,
        AVFormatContext **out_fmt_ctx,
        AVCodecContext **enc_ctx)
    {
        RETURN_IF_NULL(enc_ctx);
        int hr = -1;
    
        AVCodecContext *codec_ctx = NULL;
        hr = open_output_file(file_name, dec_ctx->codec_type, out_fmt_ctx, &codec_ctx);
        RETURN_IF_FAILED(hr);
        
        hr = init_audio_encoder(dec_ctx->sample_rate, *out_fmt_ctx, 0, codec_ctx);
        GOTO_LABEL_IF_FAILED(hr, OnErr);
    
        *enc_ctx = codec_ctx;
        return 0;
    
    OnErr:
        avcodec_free_context(&codec_ctx);
        avio_closep(&(*out_fmt_ctx)->pb);
        avformat_free_context(*out_fmt_ctx);
        *out_fmt_ctx = NULL;
        *enc_ctx = NULL;
        return hr;
    }
    

    open_output_file 函数

    通过文件后缀名 guess 一个最适合的编码器。

    int open_output_file(
        const char *file_name,
        AVMediaType stream_type,
        AVFormatContext **out_fmt_ctx,
        AVCodecContext **enc_ctx )
    {
        RETURN_IF_NULL(file_name);
        RETURN_IF_NULL(out_fmt_ctx);
        RETURN_IF_NULL(enc_ctx);
        int hr = -1;
    
        AVIOContext *output_io_ctx = NULL;
        /** Open the output file to write to it. */
        hr = avio_open(&output_io_ctx, file_name, AVIO_FLAG_WRITE);
        RETURN_IF_FAILED(hr);
    
        /** Create a new format context for the output container format. */
        *out_fmt_ctx = avformat_alloc_context();
        RETURN_IF_NULL(*out_fmt_ctx);
    
        /** Associate the output file (pointer) with the container format context. */
        (*out_fmt_ctx)->pb = output_io_ctx;
    
        /** Guess the desired container format based on the file extension. */
        (*out_fmt_ctx)->oformat = av_guess_format(NULL, file_name, NULL);
        GOTO_LABEL_IF_NULL((*out_fmt_ctx)->oformat, OnErr);
    
        char*& url = (*out_fmt_ctx)->url;
        if (NULL == url)
            url = av_strdup(file_name); 
    
        /** Find the encoder to be used by its name. */
        AVCodecID out_codec_id = AV_CODEC_ID_NONE;
        switch (stream_type) {
        case AVMEDIA_TYPE_AUDIO:
            out_codec_id = (*out_fmt_ctx)->oformat->audio_codec;
            break;
        }
        
        int stream_idx = add_stream_and_alloc_enc(out_codec_id, *out_fmt_ctx, enc_ctx);
        GOTO_LABEL_IF_FALSE(stream_idx >= 0, OnErr);
    
        return 0;
    OnErr:
        avio_closep(&(*out_fmt_ctx)->pb);
        avformat_free_context(*out_fmt_ctx);
        *out_fmt_ctx = NULL;
        *enc_ctx = NULL;
        return hr;
    }
    

    init_audio_encoder 函数

    初始化音频的一些基本参数如:声道、采样率、比特率、时间戳基准等。

    int init_audio_encoder(
        int sample_rate, 
        const AVFormatContext* out_fmt_ctx,
        unsigned int audio_stream_idx,
        AVCodecContext *codec_ctx,
        AVSampleFormat sample_fmt = AV_SAMPLE_FMT_NONE,
        uint64_t channel_layout = AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT,
        int64_t bit_rate = 64000 )
    {
        int hr = -1;
        RETURN_IF_NULL(codec_ctx);
        RETURN_IF_NULL(out_fmt_ctx);
        RETURN_IF_FALSE(audio_stream_idx < out_fmt_ctx->nb_streams);
    
         /**
         * Set the basic encoder parameters.
         * The input file's sample rate is used to avoid a sample rate conversion.
         */
        codec_ctx->channel_layout = channel_layout;
        codec_ctx->channels       = av_get_channel_layout_nb_channels(channel_layout);
        codec_ctx->sample_rate    = sample_rate;
        codec_ctx->sample_fmt     = (sample_fmt != AV_SAMPLE_FMT_NONE) ? sample_fmt : codec_ctx->codec->sample_fmts[0];
        codec_ctx->bit_rate       = bit_rate;
        /** Allow the use of the experimental encoder */
        codec_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
        codec_ctx->time_base.den  = sample_rate;
        codec_ctx->time_base.num  = 1;
        /**
         * Some container formats (like MP4) require global headers to be present
         * Mark the encoder so that it behaves accordingly.
         */
        if (out_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    
        AVStream* stream = out_fmt_ctx->streams[audio_stream_idx];
        stream->time_base = codec_ctx->time_base;
    
        /** Open the encoder for the audio stream to use it later. */
        hr = avcodec_open2(codec_ctx, codec_ctx->codec, NULL);
        RETURN_IF_FAILED(hr);
    
        hr = avcodec_parameters_from_context(stream->codecpar, codec_ctx);
        RETURN_IF_FAILED(hr);
    
        return 0;
    }
    

    audio_transcode 函数

    理论上我们采集的是 Raw PCM 数据,不需要 decode,直接可以 encode,但考虑到要使用 fifo 和 resampler,且为了和真正的 transcoding 共用代码,故封装成了 audio_transcode 函数并在采集时也调用。

    注意:解码后的音频数据不能直接编码,而是要经过一个 FIFO(先入先出队列),这是因为音频编解码的 frame 大小往往是不一样的(视频 frame 大小狭义上是一样的,但格式(RGB,YUV 等)可能不一样)。

    int audio_transcode(
        AVFormatContext* in_fmt_ctx,
        AVCodecContext* dec_ctx,
        AVFormatContext* out_fmt_ctx,
        AVCodecContext* enc_ctx,
        AVAudioFifo* fifo, 
        SwrContext* resample_ctx,
        int aud_stream_index,
        int* finished,
        bool interleaved = false,
        bool init_pts = false)
    {
        int hr = -1;
        audio_base_info out_aud_info(enc_ctx);
    
        hr = decode_a_frame(in_fmt_ctx, dec_ctx, &out_aud_info, fifo, resample_ctx, aud_stream_index, finished);
        RETURN_IF_FAILED(hr);
    
        /**
        * If we have enough samples for the encoder, we encode them.
        * At the end of the file, we pass the remaining samples to the encoder.
        */
        while (av_audio_fifo_size(fifo) >= enc_ctx->frame_size ||
                (*finished && av_audio_fifo_size(fifo) > 0)) {
            /**
            * Take one frame worth of audio samples from the FIFO buffer,
            * encode it and write it to the output file.
            */
            hr = load_encode_and_write(fifo, out_fmt_ctx, enc_ctx, &out_aud_info, interleaved, init_pts);
            RETURN_IF_FAILED(hr);
        }
    
        /**
        * If we are at the end of the input file and have encoded
        * all remaining samples, we can exit this loop and finish.
        */
        if (*finished)
            flush_encoder(out_fmt_ctx, enc_ctx, interleaved, init_pts);
    
        return 0;
    }
    

    decode_a_frame 函数

    此处只是解码的外层 wrapper。

    int decode_a_frame(
        AVFormatContext* in_fmt_ctx,
        AVCodecContext* dec_ctx,
        audio_base_info* out_aud_info,    
        AVAudioFifo* fifo,
        SwrContext* resample_ctx,
        int audio_stream_index,
        int* finished)
    {
        int hr = AVERROR_EXIT;
    
        /* Make sure that there is one frame worth of samples in the FIFO
        * buffer so that the encoder can do its work.
        * Since the decoder's and the encoder's frame size may differ, we
        * need to FIFO buffer to store as many frames worth of input samples
        * that they make up at least one frame worth of output samples. */
        while (av_audio_fifo_size(fifo) < out_aud_info->frame_size) {
            /* Decode one frame worth of audio samples, convert it to the
            * output sample format and put it into the FIFO buffer. */
            hr = read_decode_convert_and_store(fifo, in_fmt_ctx, dec_ctx, out_aud_info,
                resample_ctx, audio_stream_index, finished);
            RETURN_IF_FAILED(hr);
    
            if (*finished)
                break;
        }
        return hr;
    }
    
    read_decode_convert_and_store 函数

    继续 wrapper。

    int read_decode_convert_and_store(
        AVAudioFifo *fifo,
        AVFormatContext *in_fmt_ctx,
        AVCodecContext *dec_ctx,
        audio_base_info* out_aud_info,
        SwrContext *resampler_ctx,
        int audio_stream_index,
        int *finished)
    {
        RETURN_IF_NULL(finished);
    
        /** Temporary storage of the input samples of the frame read from the file. */
        std::vector<AVFrame*> decoded_frames;
        int hr = AVERROR_EXIT;
    
        /** Decode one frame worth of audio samples. */
        hr = decode_av_frame(in_fmt_ctx, dec_ctx, audio_stream_index, decoded_frames, finished);
        /**
         * If we are at the end of the file and there are no more samples
         * in the decoder which are delayed, we are actually finished.
         * This must not be treated as an error.
         */
        if (*finished && decoded_frames.empty()) {
            hr = 0;
            goto RESOURCE_FREE;
        }
    
        if (FAILED(hr) && decoded_frames.empty())
            GOTO_IF_FAILED(hr);
    
        /** If there is decoded data, convert and store it */
        for (size_t i = 0; i < decoded_frames.size(); ++i) {
            AVFrame* frame = decoded_frames[i];
            hr = resample_and_store(frame, dec_ctx->sample_rate, out_aud_info, resampler_ctx, fifo);
            GOTO_IF_FAILED(hr);
        }
    
        hr = 0;
    RESOURCE_FREE:
        for (size_t i = 0; i < decoded_frames.size(); ++i)
            av_frame_free(&decoded_frames[i]);
    
        return hr;
    }
    
    decode_av_frame 函数

    终于找到你了,亲爱的解码函数,不过她其实也是 FFmpeg 的终极 wrapper -_-!
    注意:此处已经抛弃了 legacy 的 avcodec_decode_audio4,而是使用 avcodec_send_packet 和 avcodec_receive_frame,具体请参考 官方文档

    int decode_av_frame(
        AVFormatContext *in_fmt_ctx,
        AVCodecContext *dec_ctx,
        int stream_index, // -1 means any stream
        std::vector<AVFrame*>& frames,
        int *finished)
    {
        RETURN_IF_NULL(in_fmt_ctx);
        RETURN_IF_NULL(dec_ctx);
        RETURN_IF_NULL(finished);
        *finished = 0;
        AVFrame *frame = NULL;
        /** Packet used for temporary storage. */
        AVPacket in_pkt;
        int hr = -1;
    
        init_packet(&in_pkt);
    
        while (true) {
            /** Read one frame from the input file into a temporary packet. */
            hr = av_read_frame(in_fmt_ctx, &in_pkt);
            if (FAILED(hr)) {
                /** If we are at the end of the file, flush the decoder below. */
                if (hr == AVERROR_EOF)
                    *finished = 1;
            }
            else if ((stream_index >= 0) && (in_pkt.stream_index != stream_index))
                continue;
            else
                av_packet_rescale_ts(&in_pkt, 
                    in_fmt_ctx->streams[in_pkt.stream_index]->time_base,
                    dec_ctx->time_base);
    
            hr = avcodec_send_packet(dec_ctx, *finished ? NULL : &in_pkt);
            if (SUCCEEDED(hr) || (hr == AVERROR(EAGAIN))) {
                while (true) {
                    /** Initialize temporary storage for one input frame. */
                    frame = av_frame_alloc();
                    GOTO_IF_NULL(frame);
    
                    hr = avcodec_receive_frame(dec_ctx, frame);
                    if (SUCCEEDED(hr))
                        frames.push_back(frame);
                    else if (hr == AVERROR_EOF) {
                        *finished = 1;
                        break;
                    }
                    else if (hr == AVERROR(EAGAIN)) // need more packets
                        break;
                    else
                        GOTO_IF_FAILED(hr);
                }
            }
            else if (hr == AVERROR_EOF)
                *finished = 1;
            else
                GOTO_IF_FAILED(hr);
    
            if (*finished || !frames.empty())
                break;
        }
    
        hr = 0;
    RESOURCE_FREE:
        // free resources
        return hr;
    }
    

    load_encode_and_write 函数

    无止境的 wrapper,从 fifo 队列中读取目标大小的数据然后进行编码并写到文件中。

    int load_encode_and_write(
        AVAudioFifo* fifo,
        AVFormatContext* out_fmt_ctx,
        AVCodecContext* enc_ctx,
        audio_base_info* out_aud_info,
        bool interleaved,
        bool init_pts = true )
    {
        int hr = -1;
        /** Temporary storage of the output samples of the frame written to the file. */
        AVFrame *output_frame = NULL;
    
        hr = read_samples_from_fifo(fifo, out_aud_info, &output_frame);
        GOTO_IF_FAILED(hr);
    
        /** Encode one frame worth of audio samples. */
        int data_written = 0;
        hr = encode_av_frame(output_frame, out_fmt_ctx, enc_ctx, 
            &data_written, interleaved, init_pts);
        GOTO_IF_FAILED(hr);
    
        hr = 0;
    RESOURCE_FREE:
        if (NULL != output_frame)
            av_frame_free(&output_frame);
        return hr;
    }
    
    read_samples_from_fifo 函数

    顾名思义,不解释。

    int read_samples_from_fifo(AVAudioFifo* fifo, audio_base_info* out_aud_info, AVFrame** output_frame)
    {
        RETURN_IF_NULL(fifo);
        RETURN_IF_NULL(out_aud_info);
        RETURN_IF_NULL(output_frame);
        int hr = -1;
    
        /**
         * Use the maximum number of possible samples per frame.
         * If there is less than the maximum possible frame size in the FIFO
         * buffer use this number. Otherwise, use the maximum possible frame size
         */
        int fifo_size = av_audio_fifo_size(fifo);
        const int frame_size = FFMIN(fifo_size, out_aud_info->frame_size);
    
        /** Initialize temporary storage for one output frame. */
        hr = init_audio_frame(output_frame, out_aud_info);
        RETURN_IF_FAILED(hr);
    
        /**
         * Read as many samples from the FIFO buffer as required to fill the frame.
         * The samples are stored in the frame temporarily.
         */
        int samples_read = av_audio_fifo_read(fifo, (void**)((*output_frame)->data), frame_size);
        RETURN_IF_FALSE(samples_read == frame_size);
    
        return 0;
    }
    
    encode_av_frame 函数

    亲爱的编码函数。

    int encode_av_frame(
        AVFrame *frame,
        AVFormatContext *out_fmt_ctx,
        AVCodecContext *enc_ctx,
        int* data_written,
        bool interleaved,
        bool init_pts)
    {
        // frame can be NULL which means to flush
        RETURN_IF_NULL(out_fmt_ctx);
        RETURN_IF_NULL(enc_ctx);
        RETURN_IF_NULL(data_written);
        *data_written = 0;
        int hr = -1;
    
        if (NULL != frame && init_pts) {
            if (enc_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
                /** Set a timestamp based on the sample rate for the container. */
                frame->pts = g_ttl_a_samples;
                g_ttl_a_samples += frame->nb_samples;
            }
        }
    
        int stream_idx = 0;
        for (unsigned int i = 0; i < out_fmt_ctx->nb_streams; ++i) {
            if (out_fmt_ctx->streams[i]->codecpar->codec_type == enc_ctx->codec_type) {
                stream_idx = i;
                break;
            }
        }
    
        std::vector<AVPacket*> packets;
        AVPacket* output_packet = NULL;
    
        hr = avcodec_send_frame(enc_ctx, frame);
        if (SUCCEEDED(hr) || (hr == AVERROR(EAGAIN))) {
            while (true) {
                /** Packet used for temporary storage. */
                output_packet = new AVPacket();
                init_packet(output_packet);
    
                hr = avcodec_receive_packet(enc_ctx, output_packet);
                if (SUCCEEDED(hr)) {
                    output_packet->stream_index = stream_idx;
                    packets.push_back(output_packet);
                }
                else if (hr == AVERROR(EAGAIN)) // need more input frames
                    break;
                else if (hr == AVERROR_EOF)
                    break;
                else
                    GOTO_IF_FAILED(hr);
            }
        }
        else if (hr != AVERROR_EOF)
            GOTO_IF_FAILED(hr);
    
        for (size_t i = 0; i < packets.size(); ++i) {
            // set pts based on stream time base.
            AVRational stream_tb = get_stream_time_base(out_fmt_ctx, enc_ctx->codec_type);
            AVPacket* packet = packets[i];
            switch (enc_ctx->codec_type) {
            case AVMEDIA_TYPE_AUDIO:
                av_packet_rescale_ts(packet, enc_ctx->time_base, stream_tb);
                break;
            }
    
            /** Write one frame from the temporary packet to the output file. */
            if (interleaved)
                hr = av_interleaved_write_frame(out_fmt_ctx, packet);
            else
                hr = av_write_frame(out_fmt_ctx, packet);
            GOTO_IF_FAILED(hr);
    
            *data_written = 1;
        }
    
        hr = 0;
    RESOURCE_FREE:
        // free resources
        return hr;
    }
    

    flush_encoder 函数

    终于结束了,最后是擦屁股。

    int flush_encoder(
        AVFormatContext* format_ctx,
        AVCodecContext* codec_ctx,
        bool interleaved,
        bool init_pts)
    {
        if (!(codec_ctx->codec->capabilities & AV_CODEC_CAP_DELAY))
            return 0;
    
        int data_written = 0;
        /** Flush the encoder as it may have delayed frames. */
        do {
            int hr = encode_av_frame(NULL, format_ctx, codec_ctx, &data_written, interleaved, init_pts);
            RETURN_IF_FAILED(hr);
        } while (data_written);
    
        return 0;
    }
    

    其他框架下的采集

    请参考对应的文章。

    Blueware
    EOF

    展开全文
  • sudo gedit ffserver.conf[sudo] password for huzia: huzia@huzia-laptop:~/ffmpeg$ ffserver -f ffserver.conf & ffmpeg -f oss -i /dev/dsp http://192.168.0.1:8090/feed1.ffmFFserver version git-60ff1c
  • 如果已经完成FFMPEG录制视频保存到本地的功能,完成RTMP推只需要修改几行代码即可完成。 主要修改的代码: filename="rtmp://js.live-send.acg.tv/live-js/?xxxxxxxx" avformat_alloc_output_context2(&oc...
  • Windows下使用ffmpeg采集音频视频

    千次阅读 2019-01-15 20:44:52
    本文介绍一下ffmpeg在windows下采集音频的相关命令。 一开始在命令行下使用ffmpeg时执行“ffmpeg -list_devices true -f dshow -i dummy ”使用dshow来枚举当前系统上存在的音视频采集设备时,发现中文乱码,后来在...
  • 如何使用ffmpeg命令采集音频。 如何使用ffmpeg代码采集音频。 软硬件环境 使用ffmpeg采集音频数据,主要需要2个软硬件环境: 硬件环境,需要有一个录音设备,我这里使用的是麦克风,并且插入了机箱后面的粉色...
  • ffmpeg 转换音频采集

    千次阅读 2017-10-12 19:53:00
    ffmpeg官方下载地址:http://ffmpeg.org/download.html 查看音频信息: ffprobe -v quiet -print_format json -show_streams 文件名 { "streams": [ { ...
  • 之前一直用Directshow技术采集摄像头数据,但是觉得涉及的细节比较多,要开发者比较了解Directshow的框架知识,学习起来有...如果能用FFmpeg实现采集、编码和录制(或推),那整个实现方案就简化很多,正因为这个...
  • Windows下使用ffmpeg采集音频或视频

    千次阅读 2018-12-24 16:08:25
    1、一开始在命令行下使用ffmpeg时执行“ffmpeg -list_devices true -f dshow -i dummy ”使用dshow来枚举当前系统上存在的音视频采集设备时,发现中文乱码,后来在老师帮助下,在命令行下执行命令“ chcp 65001” 将...
  • 本地搭建直播媒体服务 LiveQing直播点播媒体服务下载 利用ffmpeg查看采集设备 ffmpeg -hide_banner -list_devices...音频采集 ffmpeg -f dshow -i audio="麦克风阵列 (Realtek High Definition Audio)" -cod...
  • 本文记录针对FFMPEG音频采集的方法和基本流程并保存为文件供学习 环境:centos8虚拟机+ffmpeg 命令行采集: ffmpeg -f alsa -ac 2 -i hw:0,0 out.wav 可以看到命令采用alsa进行设备管理随后采集通道为hw:0,0 代码段...
  • ffmpeg采集麦克风音频编码为G711或者AAC,需要指定麦克风设备。
  • 使用FFmpeg API采集摄像头图像和麦克风音频,支持图像预览,把图像和音频编码后保存成文件,实现视音频同步机制。为更好理解代码,请阅读我博客上相关的一篇文章:...
  • linux FFMPEG 摄像头采集数据推

    千次阅读 2018-07-31 11:01:56
    1)搭建推服务器Nginx-rtmp(主要参考:https://www.cnblogs.com/cocoajin/p/4353767.html) 下载源码 mkdir /home/ffmpeg cd /home/ffmpeg wget http://nginx.org/download/nginx-1.7.5.tar.gz wget htt...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,918
精华内容 1,967
关键字:

ffmpeg音频采集流化