FFmpeg开发实战(四):FFmpeg 抽取音视频的音频数据

使用FFmpeg抽取音频数据为ACC文件之前,先了解一下AAC的相关概念及基本的文件封装格式方式。

学习链接为: 音视频编解码技术(二):AAC 音频编码技术

使用FFmpeg抽取音视频的音频数据为ACC文件,代码如下:

#include "iostream"
#include "string"

extern "C"
{
#include "dshow.h"
#include "libavutil/opt.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/imgutils.h"
#include "libavutil/mathematics.h"
#include "libavutil/samplefmt.h"
#include "libavutil/time.h"
#include "libavutil/fifo.h"
#include "libavdevice/avdevice.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
}

void adts_header(char *szAdtsHeader, int dataLen);

// 使用FFmpeg从视频中抽取音频
void extractAudio()
{

    // 设置日志输出等级
    av_log_set_level(AV_LOG_INFO);

    AVFormatContext *fmt_ctx = NULL;
    AVPacket pkt;

    av_register_all();

    int ret;
    int len;
    int audio_index = -1;

    // 打开输入文件
    ret = avformat_open_input(&fmt_ctx, "http://vfx.mtime.cn/Video/2019/03/17/mp4/190317150237409904.mp4", NULL, NULL);

    // 检查打开输入文件是否成功
    if (ret < 0)
    {
        printf("cant open file,error message = %d\n", ret);
        return;
    }

    // 打开输出文件
    FILE *dst_fd = fopen("D:/111.aac", "wb"); // w 写入  b 二进制文件

    // 检查输出文件打开是否成功,如果失败,就输出日志,并关闭输出文件的引用
    if (!dst_fd)
    {
        av_log(NULL, AV_LOG_ERROR, "Can't Open Out File!\n");
        avformat_close_input(&fmt_ctx);
    }

    // 获取到音频流
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;

    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    for (int i = 0; i < fmt_ctx->nb_streams; i++)
    {
        if (fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            audio_index = i;
            break;
        }
    }

    printf("Audio Stream Index = %d", audio_index);

    // 检查发现音频流的结果
    if (audio_index < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Can't find Best Audio Stream!\n");
        //printf("Reason = %s", av_err2str(ret));
        // 关闭输出文件和输出文件的引用
        avformat_close_input(&fmt_ctx);
        fclose(dst_fd);
        return;
    }

    while (av_read_frame(fmt_ctx, &pkt) >= 0)
    {
        if (pkt.stream_index == audio_index)
        {
            printf("Has Read An Audio Packet\n");
            char adts_header_buf[7];
            adts_header(adts_header_buf, pkt.size);
            fwrite(adts_header_buf, 1, 7, dst_fd);
            len = fwrite(pkt.data, 1, pkt.size, dst_fd);
            if (len != pkt.size)
            {
                av_log(NULL, AV_LOG_WARNING, "Waring! Length of data not equal size of pkt!\n");
            }
        }
        // 将引用基数减一
        av_packet_unref(&pkt);
        //av_free_packet(&pkt);
    }

    // 关闭文件(输入/输出)
    avformat_close_input(&fmt_ctx);
    if (dst_fd)
    {
        fclose(dst_fd);
    }
}

void adts_header(char *header, int dataLen)
{
    // aac级别
    int aac_type = 2 - 1;
    // 采样率下标
    /** 0: 96000 Hz
     1: 88200 Hz
     2: 64000 Hz
     3: 48000 Hz
     4: 44100 Hz
     5: 32000 Hz
     6: 24000 Hz
     7: 22050 Hz
     8: 16000 Hz
     9: 12000 Hz
     10: 11025 Hz
     11: 8000 Hz
     12: 7350 Hz */
    // 采样率下标:LC格式的为正常索引,HE格式的索引为除2后对应的采样索引
    // 因为:HE使用了SBR技术,即 Spectral Band Replication(频段复制),所以存储同样的音频内容,HE文件较小。使用时采样率为LC的一半。
    int sampling_frequency_index = 4;  
    // 声道数
    int channel_config = 2;

    // ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。
    int adtsLen = dataLen + 7;

    // syncword,标识一个帧的开始,固定为0xFFF,占12bit(byte0占8位,byte1占前4位)
    header[0] = 0xff;
    header[1] = 0xf0;

    // ID,MPEG 标示符。0表示MPEG-4,1表示MPEG-2。占1bit(byte1第5位)
    header[1] |= (0 << 3);

    // layer,固定为0,占2bit(byte1第6、7位)
    header[1] |= (0 << 1);

    // protection_absent,标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。占1bit(byte1第8位)
    header[1] |= 1;

    // profile,标识使用哪个级别的AAC。1: AAC Main 2:AAC LC 3:AAC SSR 4:AAC LTP。占2bit(byte2第1、2位)
    header[2] = aac_type << 6;

    // sampling_frequency_index,采样率的下标。占4bit(byte2第3、4、5、6位)
    header[2] |= (sampling_frequency_index & 0x0f) << 2;

    // private_bit,私有位,编码时设置为0,解码时忽略。占1bit(byte2第7位)
    header[2] |= (0 << 1);

    // channel_configuration,声道数。占3bit(byte2第8位和byte3第1、2位)
    header[2] |= (channel_config & 0x04) >> 2;
    header[3] = (channel_config & 0x03) << 6;

    // original_copy,编码时设置为0,解码时忽略。占1bit(byte3第3位)
    header[3] |= (0 << 5);

    // home,编码时设置为0,解码时忽略。占1bit(byte3第4位)
    header[3] |= (0 << 4);

    // copyrighted_id_bit,编码时设置为0,解码时忽略。占1bit(byte3第5位)
    header[3] |= (0 << 3);

    // copyrighted_id_start,编码时设置为0,解码时忽略。占1bit(byte3第6位)
    header[3] |= (0 << 2);

    // aac_frame_length,ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。占13bit(byte3第7、8位,byte4全部,byte5第1-3位)
    header[3] |= ((adtsLen & 0x1800) >> 11);
    header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);
    header[5] = (uint8_t)((adtsLen & 0x7) << 5);

    // adts_buffer_fullness,固定为0x7FF。表示是码率可变的码流 。占11bit(byte5后5位,byte6前6位)
    header[5] |= 0x1f;
    header[6] = 0xfc;

    // number_of_raw_data_blocks_in_frame,值为a的话表示ADST帧中有a+1个原始帧,(一个AAC原始帧包含一段时间内1024个采样及相关数据)。占2bit(byte6第7、8位)。
    header[6] |= 0;
}

int main(int args, char *argv[])
{
    extractAudio();
}

 

posted @ 2019-02-18 21:08  灰色飘零  阅读(2021)  评论(0)    收藏  举报