-
2018-10-04 14:30:50
本篇文章将介绍使用FFMpeg解码音频文件为PCM的数据。
使用FFMpeg获取想要的音频数据的步骤如下:
解封装(MP3文件)->解码(MP3编码)->PCM数据重采样1. 解封装
使用FFMpeg解封装的步骤如下:
- 使用函数 av_register_all() 注册所有的封装器和解封装器。
- 使用函数 avformat_open_input() 打开一个文件,可以为文件名也可以为一个URL。
- 使用函数 avformat_find_stream_info() 查找流信息,把它存入AVFormatContext中。
- 查找流信息,获取音频流的索引位置,获取解码器的codec_id。
- 根据codec_id,使用函数 avcodec_find_decoder() 获取解码器AVCodec*。
- 使用函数 avcodec_open2() 打开解码器。
下面是关键部分代码:
bool MusicDecodecThread::openAudioFile(QString fileName) { av_register_all(); m_AvFrame = av_frame_alloc(); // 打开文件 int result = avformat_open_input(&m_AVFormatContext, fileName.toLocal8Bit().data(), nullptr, nullptr); if (result != 0 || m_AVFormatContext == nullptr) return false; // 查找流信息,把它存入AVFormatContext中 if (avformat_find_stream_info(m_AVFormatContext, nullptr) < 0) return false; int streamsCount = m_AVFormatContext->nb_streams; // 读取详细信息 AVDictionaryEntry *tag = nullptr; while (tag = av_dict_get(m_AVFormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) { QString keyString = tag->key; QString valueString = QString::fromUtf8(tag->value); m_InfoMap.insert(keyString, valueString); } // 查找音频流索引 for (int i=0; i<streamsCount; ++i) { if (m_AVFormatContext->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { AVPacket pkt = m_AVFormatContext->streams[i]->attached_pic; m_InfoImage = QImage::fromData((uchar*)pkt.data, pkt.size); } if (m_AVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { m_AudioIndex = i; continue; } } if (m_AudioIndex == -1) return false; // 获取总时间 m_TotalTime = m_AVFormatContext->duration / AV_TIME_BASE * 1000; // 查找解码器 m_AudioCodec = m_AVFormatContext->streams[m_AudioIndex]->codec; AVCodec *codec = avcodec_find_decoder(m_AudioCodec->codec_id); if (codec == nullptr) return false; // 打开音频解码器 if (avcodec_open2(m_AudioCodec, codec, nullptr) != 0) return false; int rate = m_AudioCodec->sample_rate; int channel = m_AudioCodec->channels; m_AudioCodec->channel_layout = av_get_default_channel_layout(m_AudioCodec->channels); return true; }
其中:
AVFormatContext *m_AVFormatContext = nullptr;
AVCodecContext *m_AudioCodec = nullptr;AVFormatContext中存储文件封装的信息,比如我们可以使用这个方法获取音频的总时间长度:
m_TotalTime = m_AVFormatContext->duration / AV_TIME_BASE * 1000;AVCodecContext 中存储了与解码器相关的信息,如
sample_rate: 表示采样率
channels: 表示通道数
sample_fmt: 表示采样的格式。(如AV_SAMPLE_FMT_S16、AV_SAMPLE_FMT_S32等)2. 解码
- 使用函数 av_read_frame() 获取一包数据。
- 使用函数 avcodec_send_packet() 发送一包数据。
- 使用函数 avcodec_receive_frame() 获取一帧数据。
下面是关键部分代码:
while (!this->isInterruptionRequested()) { QMutexLocker locker(&m_Mutex); AVPacket pkt; int result = av_read_frame(m_AVFormatContext, &pkt); if (result != 0) { QThread::msleep(10); continue; } if (pkt.stream_index != m_AudioIndex) continue; // 解码音频帧, 发送音频包 if (avcodec_send_packet(m_AudioCodec, &pkt)) continue; // 解码音频帧,接收音频解码帧 if (avcodec_receive_frame(m_AudioCodec, m_AvFrame)) continue; // 释放包的内存 av_packet_unref(&pkt); }
m_AvFrame->data 就存储着解码后的数据。
3. 对解码后的数据重采样
这里使用的FFMpeg提供工具(SwrContext)对音频做重采样。使用方法如下:
- 使用方法 swr_alloc(); 创建一个 SwrContext* 类型的指针,并分配内存。
- 使用方法 swr_alloc_set_opts() 设置输入和输出的参数。
- 使用方法 swr_init() 初始化这个 SwrContext* 指针变量。
- 使用方法 swr_convert() 转换。
- 使用方法 swr_free() 释放内存。
下面是主要部分代码:
SwrContext *m_SWRtx = swr_alloc(); swr_alloc_set_opts(m_SWRtx, m_AudioCodec->channel_layout, AV_SAMPLE_FMT_S16, \ m_AudioCodec->sample_rate, m_AudioCodec->channels, m_AudioCodec->sample_fmt, \ m_AudioCodec->sample_rate, 0, 0); swr_init(m_SWRtx); uint8_t *array[1]; uint8_t arrays[10000] = {0}; array[0] = arrays; int len = swr_convert(m_SWRtx, array, 10000, (const uint8_t **)m_AvFrame->data, \ m_AvFrame->nb_samples); swr_free(&m_SWRtx);
我这里使用线程解码,完整代码如下:
MusicDecodecThread.h#ifndef MUSCI_DECODEC_THREAD_H #define MUSCI_DECODEC_THREAD_H #include <QThread> #include <QObject> #include <QMap> #include <QImage> #include <QMutex> #include <QMutexLocker> #include "AudioPlayThread.h" extern "C"{ #include <stdio.h> #include <stdlib.h> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/frame.h> #include <libswscale/swscale.h> #include <libswresample/swresample.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> #include <libavutil/opt.h> #include <libavutil/error.h> } class MusicDecodecThread : public QThread { Q_OBJECT public: MusicDecodecThread(QObject *parent = nullptr); ~MusicDecodecThread(); // 打开文件 bool openAudioFile(QString fileName); void run(void) override; // 获取信息列表中的内容 QMap<QString, QString> getInfoMap(void); // 获取音乐的头像 QImage getMusicIcon(void); // 获取音乐的总时长 int getTotalTime(void); private: AVFormatContext *m_AVFormatContext = nullptr; AVCodecContext *m_AudioCodec = nullptr; AVFrame *m_AvFrame; int m_AudioIndex = -1; int m_TotalTime = 0; QMap<QString, QString> m_InfoMap; QImage m_InfoImage; QMutex m_Mutex; }; #endif
MusicDecodecThread.cpp
#include "MusicDecodecThread.h" #include <QDebug> #include <QTime> MusicDecodecThread::MusicDecodecThread(QObject *parent) :QThread(parent) { av_register_all(); avfilter_register_all(); m_AvFrame = av_frame_alloc(); g_AudioPlayThread->start(); } MusicDecodecThread::~MusicDecodecThread() { } bool MusicDecodecThread::openAudioFile(QString fileName) { QMutexLocker locker(&m_Mutex); if (m_AVFormatContext) avformat_close_input(&m_AVFormatContext); // 打开文件 int result = avformat_open_input(&m_AVFormatContext, fileName.toLocal8Bit().data(), nullptr, nullptr); if (result != 0 || m_AVFormatContext == nullptr) return false; // 查找流信息,把它存入AVFormatContext中 if (avformat_find_stream_info(m_AVFormatContext, nullptr) < 0) return false; int streamsCount = m_AVFormatContext->nb_streams; // 读取详细信息 AVDictionaryEntry *tag = nullptr; while (tag = av_dict_get(m_AVFormatContext->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) { QString keyString = tag->key; QString valueString = QString::fromUtf8(tag->value); m_InfoMap.insert(keyString, valueString); } // 查找音频流索引 for (int i=0; i<streamsCount; ++i) { if (m_AVFormatContext->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { AVPacket pkt = m_AVFormatContext->streams[i]->attached_pic; m_InfoImage = QImage::fromData((uchar*)pkt.data, pkt.size); } if (m_AVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { m_AudioIndex = i; continue; } } if (m_AudioIndex == -1) return false; // 获取总时间 m_TotalTime = m_AVFormatContext->duration / AV_TIME_BASE * 1000; // 查找解码器 m_AudioCodec = m_AVFormatContext->streams[m_AudioIndex]->codec; AVCodec *codec = avcodec_find_decoder(m_AudioCodec->codec_id); if (codec == nullptr) return false; // 打开音频解码器 if (avcodec_open2(m_AudioCodec, codec, nullptr) != 0) return false; int rate = m_AudioCodec->sample_rate; int channel = m_AudioCodec->channels; m_AudioCodec->channel_layout = av_get_default_channel_layout(m_AudioCodec->channels); g_AudioPlayThread->cleanAllAudioBuffer(); g_AudioPlayThread->setCurrentSampleInfo(rate, 16, channel); return true; } void MusicDecodecThread::run(void) { QTime time; int count = 0; while (!this->isInterruptionRequested()) { QMutexLocker locker(&m_Mutex); AVPacket pkt; int result = av_read_frame(m_AVFormatContext, &pkt); if (result != 0) { QThread::msleep(10); continue; } if (pkt.stream_index != m_AudioIndex) continue; // 解码视频帧, 发送视频包 if (avcodec_send_packet(m_AudioCodec, &pkt)) continue; // 解码视频帧,接收视频解码帧 if (avcodec_receive_frame(m_AudioCodec, m_AvFrame)) continue; SwrContext *m_SWRtx = swr_alloc(); swr_alloc_set_opts(m_SWRtx, m_AudioCodec->channel_layout, AV_SAMPLE_FMT_S16, \ m_AudioCodec->sample_rate, m_AudioCodec->channels, m_AudioCodec->sample_fmt, \ m_AudioCodec->sample_rate, 0, 0); swr_init(m_SWRtx); uint8_t *array[1]; uint8_t arrays[10000] = {0}; array[0] = arrays; int len = swr_convert(m_SWRtx, array, 10000, (const uint8_t **)m_AvFrame->data, m_AvFrame->nb_samples); g_AudioPlayThread->addAudioBuffer((char*)arrays, m_AvFrame->linesize[0]); swr_free(&m_SWRtx); av_packet_unref(&pkt); } } QMap<QString, QString> MusicDecodecThread::getInfoMap(void) { QMutexLocker locker(&m_Mutex); return m_InfoMap; } QImage MusicDecodecThread::getMusicIcon(void) { QMutexLocker locker(&m_Mutex); return m_InfoImage; } int MusicDecodecThread::getTotalTime(void) { return m_TotalTime; }
更多相关内容 -
Vorbis ogg 音频编码/解码:ogg 音频编码/解码-matlab开发
2021-06-01 20:14:23包括 2 个用于写入和读取 OGG Vorbis 文件的函数。 它的工作方式类似于命令 WAVWRITE 和 WAVREAD。 1.- 解压到MATLAB目录下的toolbox文件夹即可。 2.- 设置 MATLAB 搜索路径以包含该文件夹。 此版本是在 MATLAB ... -
encdec:最小的音频文件编码器,解码器节点套接字服务器
2021-05-15 08:31:18最小的音频文件编码器/解码器节点套接字服务器。 运行LAME和FLAC命令,通过websocket传入和传出音频文件,以最大程度地减少内存使用量。 相关性: , 和 。 一旦这些在命令行上可用,就可以使用。 跑步: node ... -
aac音频文件,用于测试编解码
2020-11-20 09:57:05aac音频文件,用于测试编解码 -
g711,g726音频编解码程序,带测试文件,研究学习
2019-04-29 19:00:44g711,g726音频编解码程序,带测试文件,研究学习。 学习g711,g726挺好的工程,适合研究学习 g711 g726 -
音频视频编码解码
2013-08-16 08:38:26音频视频编码解码 mpeg -
音频和视频编码解码过滤器 Media Player Codec Pack Plus 4.5.7.115 安装版.zip
2021-05-19 09:27:37媒体播放器的解码包,支持几乎所有的压缩和使用现代化的视频和音频文件的文件类型。 该软件包是安装简单,同时还提供了高级设置到高端用户: 对于简单的安装选择“简易安装”。 对于高级安装选项中选择“专家安装”... -
winLAMEPrerelease(音频格式编码解码器)v4免费绿色版
2019-08-07 21:54:00软件功能 支持的输入模块: libsndfile 音频文件解码器 MAD MPEG 音频文件解码器 Ogg Vorbis 解码器 FLAC 音频文件解码器 BASS 音频文件解码器 CD 音频抓取 支持的输出模块: LAME mp3 编码器 Ogg Vor -
一款VB音频解码编码处理DLL动态库源代码
2021-05-07 16:36:43内容索引:VB源码,多媒体技术,多媒体 这是个动态链接库DLL的源代码,本动态库的功能主要是在音频处理方面,比如进行音频播放、音频编码和解码,它支持DSP、频谱分析以及可视化效果控制等,能实现WAV, MP3, OGG ... -
各种编码,封装格式的视音频文件
2017-09-28 11:44:23封装格式AVI、VOB、WMV、RM、RMVB、MOV、MKV、FLV、MP4、MP3、WebM、DAT、3gpp、asf、mpeg、ogg; ...音频编码格式: G.711、G722、G726、AAC、MP1/MP2/MP3、AC-3、WMA、amr_nb、amr_wb、RealAudio -
音频解码,分贝计算 适用 wav/mp3/aac 16/24/32bit的音频类型
2022-04-22 22:32:28音频解码,分贝计算 适用 wav/mp3/aac 16/24/32bit的音频类型 音频可视化 -
插件--纯前端解码、播放、录音、编码 AMR 音频,无须服务器支持
2019-02-22 17:33:35纯前端解码、播放、录音、编码 AMR 音频,无须服务器支持,基于amr.js 和 RecorderJs。 特性 方便的 API 实现解码、播放、录音、编码 AMR 文件。 支持 url 和 blob (即<input type="file">)方式获取 AMR。... -
音频编码解码器库 libZPlay
2012-11-22 13:41:59该库集成了所有支持格式(mp3, mp2, mp1, ogg, flac, ac3, aac, oga, wav and pcm )的编码解码器。库本身是由WINAPI编写,你无须额外的库,也不需要MFC / .NET的支持,只能在Windows下运行。 库直接对声卡播放音乐... -
FFmpeg提取视音频文件
2018-11-07 16:27:193、掌握和熟悉提取视音频文件的基本方法 二、实验要求 1、对ffmpeg的编译环境进行配置; 2、对一个视频文件,提取基本信息(例如,封装格式,码流,视频编码方式,音频编码方式,分辨率,帧率,时长等等),并输出... -
精通Visual C++视频/音频编解码技术
2017-10-10 11:18:02《精通Visual C++视频、音频编解码技术》全面、详细地介绍了Visual C++视频/音频编解码技术的基本原理和编程知识,并结合工程实际,给出了丰富的实例和大量的Visual C++源代码文件。全书共分为4篇16章,其中“基础篇... -
G.711-u-law编码格式音频文件,可以作为测试用
2019-01-03 14:28:48G.711u-law编码格式音频文件,可以作为测试实验用,挺好用的。 -
音频解码编码源代码
2015-01-10 16:52:17功能:读入自己的一段录音文件wav格式等,利用mp3进行编码,保存成mp3 上传内容包含:源码一套、读入的自己的录音文件、保存的mp3文件 -
ac3音频文件解码器
2012-10-11 15:20:56ac3音频文件解码器,可播放ac3音频格式文件 -
音频编码和解码原理
2016-09-13 19:49:16作者:yuyin86 视频流中的DTS/PTS到底是什么...mpeg文件中的每一个包都有一个SCR时间戳并且这个时间戳就是读取这个数据包时的系统时间。通常情况下,解码器会在它开始读取mpeg流时启动系统时钟(系统时钟的初始值 -
Wav2MP3 Wizard:Wav2MP3 Wizard 一个简单的日常音频编码/解码解决方案!-开源
2021-05-30 21:13:57Wav2MP3 向导是日常音频编码/解码的简单解决方案。 它支持 WAV、MP3、OGG 和 FLAC 文件,并具有直观的界面。 新人易于学习(带有预设配置文件),并且具有足够的高级功能。 -
单片机与DSP中的SEED-AM642多路音频编码卡
2020-12-03 12:48:47本产品主要采用的核心技术为MP3音频编码算法,其技术指标分述如下: 标准:与ISO 11172-3 Audio Encoding Layer Ⅲ 完全兼容,编码输出文件可由主流媒体播放软件(RealPlayer、Windows Media Player)直接... -
音频文件格式解析与编解码
2019-05-22 18:45:33说到语音技术就不得不说起音频数据,从硬件设备采集语音信号,语音信号的处理,到语音信号A/D转换得到原始数据(raw data),再到对原始数据进行编码得到音频文件,对音频文件解码进行播放。那么为什么会出现如此多的...一、音频格式总览
说到语音技术就不得不说起音频数据,从硬件设备采集语音信号,语音信号的处理,到语音信号A/D转换得到原始数据(raw data),再到对原始数据进行编码得到音频文件,对音频文件解码进行播放。那么为什么会出现如此多的音频格式?使用最多的几种音频格式有MP3、WMA、WAV、AAC、FLAC、APE、WV、ASF、VQF、MID、OGG、M4A、eAAC+。目前只用到了其中三种,故详细分析WAV、MP3、OGG。
二、原始数据(raw data,以PCM编码为例)
模拟音频信号经模数转换(A/D变换)直接形成的二进制序列,该文件没有附加的文件头和文件结束标志。而PCM(Pulse-code Modulation,脉冲编码调制)是一种模拟信号的数字化方法,常被用于数字电信系统中,非常频繁地,PCM编码以一种串行通信的形式,使数字传讯由一点至下一点变得更容易——不论在已给定的系统内,或物理位置。
PCM过程放在ALSA的实践中作为理论部分呈现,这里给出多通道音频数据的比特位特征。
图1 多通道音频数据的比特流格式
如图1所示,单通道音频数据以采样位数(bit)串行记录在比特流中:
1) 8 bit 采样位数: 意味着每个采样值能占据1个字节大小;
+------+------+------+------+------+------+------+------+------+ | 500 | 300 | -100 | -20 | -300 | 900 | -200 | -50 | 250 | +------+------+------+------+------+------+------+------+------+
2)16 bit 采样位数:分为两个字节以小端(little-endian)方式存储在比特流中;
双通道及多通道数据音频数据以采样位数组织如下(n 为采样位数):
+-- n bit---+---n bit---+-----+---n bit---+---n bit--+------+--n bit---+---n bit---+----------+ | channel1 | channel2 | ... | channel1 | channel2 | ... | channel1 | channel2 | ... | +----------+----------+---------+----------+---------+----------+---------+----------+----------+
PCM的每个样本值包含在一个整数i中,i的长度为容纳指定样本长度所需的最小字节数。首先存储低有效字节,表示样本幅度的位放在i的高有效位上,剩下的位置为0。
计算机读入原始音频数据的方式与打开二进制文件相同。
#include <stdio.h> #include <stdlib.h> int main() { char *fn; char *data; FILE *fp; int len; fn = "./test.pcm"; fp = fopen(fn, "rb"); if(!fp) { printf("file open failed.\n"); exit(1); } fseek(fp, 0, SEEK_END); len = ftell(fp); if(!len) { printf("file is null.\n"); fclose(fp); exit(1); } printf("file len = %d\n",len); data = (char *)malloc(len); fseek(fp, 0, SEEK_SET); fread(data, sizeof(__int16_t), len/sizeof(__int16_t), fp); fclose(fp); printf("%.*s\n",1,data); free(data); return 0; }
三、wav文件
wav(Waveform Audio FIle Format),即波形声音文件,由微软公司开发,是最早的数字音频格式,音质与CD相差无几,但缺点是wav文件非常庞大,不利于数据传输。
struct waitor_wav_format { #pragma pack(1) char riff_id[4]; // "RIFF",big-endian __uint32_t file_len; // file length,little-endian char wave_id[4]; // "WAVE“,big-endian char fmt_id[4]; // "fmt",big-endian,beginning of fmt chunk char transition[4]; // size of fmt chunk __uint16_t fmt_type; // 1-PCM __uint16_t channel; // 通道数 __uint16_t sample_rate; // 采样率 __uint32_t avg_bytes_per_sec; // sample_rate * block_align __uint16_t block_align; // 每次采样大小 __uint16_t bit_per_second; // 采样精度 //__uint16_t cbsize // 附加数据大小 char data_id[4]; // "data" __uint32_t audio_len; // 音频数据的长度 #pragma pack() };
wav文件分为3个(或4个)chunk,每个chunk基本格式为 chunk_id + chunk_size + chunk_data。总体结构为 文件头 wav_hdr + 音频数据 PCM格式。在解析wav文件时,可能会由于不同系统不同调制方式方式的不同增加几个额外的字节,这时应在数据结构中加入#pragma pack(1),以设置对齐的方式空出额外长度。
int main() { waitor_wav_t *w; char *fn, *audio; // char buffer[4096]; int ret; FILE *fp = fopen("./enc.ogg", "wb"); fn = "../data/test.wav"; w = waitor_wav_read(audio, fn); // 解析wav文件格式,转为二进制流voice if(!w) { printf("wavfile read failed.\n"); fclose(fp); free(w); exit(1); } printf(" chunk_id1 : %.*s\n", 4, w->riff_id); printf(" file len : %d\n", w->file_len); printf(" fomat : %.*s\n", 4, w->wave_id); printf(" sub chunk id : %.*s\n", 4, w->fmt_id); printf(" fmt type : %d\n", w->fmt_type); printf(" channel : %d.\n", w->channel); printf(" sample_rate : %d \n", w->sample_rate); fclose(fp); free(w); return 0; }
四、MP3文件
MP3(Moving Picture Experts Group Audio Layer III),是利用同名压缩技术,将音乐以1:10甚至1:12的压缩率压缩成容量较小的文件,并且能够保证损失音质极少,也因此成为网络音乐传输的主要格式。特点:兼具少损音质和小体积的特点,缺点是最高比特率是320K,高频部分一刀切,音质不高。
MPEG压缩有
MP3音频压缩包含编码和解码两个部分。编码是将WAV文件中的数据转换成高压缩率的位流形式,解码是接受位流并将其重建到WAV文件中。
MP3采用了感知音频编码(Perceptual Audio Coding)这一失真算法。人耳感受声音的频率范围是20Hz-220kHz,MP3截掉了大量的冗余信号和无关的信号,编码器通过混合滤波器组将原始声音变换到频率域,利用心理声学模型,估算刚好能被察觉到的噪声水平,再经过量化,转换成Huffman编码,形成MP3位流。解码器要简单得多,它的任务是从编码后的谱线成分中,经过反量化和逆变换,提取出声音信号。
在压缩音频数据时,先将原始声音数据分成固定的分块,然后作顺向MDCT变换,MDCT本身并不进行数据压缩,只是将一组时域数据转换成频域数据,以得知时域变化情况,顺向MDCT将每块的值转换为512个MDCT系数。量化使数据得到压缩,在对量化后的变换样值进行比特分配时要考虑使整个量化块最小,这就成为有损压缩了。解压时,经反向MDCT将512个系数还原成原始声音数据,前后的原始声音数据是不一致的,因为在压缩过程中,去掉了冗余和不相关数据。
MP3文件的详细格式参见以下两个博客:
libmad库常被用来作为mp3解码器,不需要自己造轮子,基于libmad的mp3解码播放器参见:
接下来总结MP3文件的格式与libmad解码应用过程。
1、MPEG压缩格式
MP3文件格式分为三个部分:
标签头和标签帧数据结构,每个ID3V2都包含一个标签头和若干标签帧,而标签帧由帧头和至少一个字节的内容组成。
ID3V2有四个版本,但流行的只有ID3V2.3:
/* ID3V2 的标签头结构 */ struct ID3V2Header { #pragma pack(1) unsigned char Header[3]; /* 保存的值比如为"ID3"表示是ID3V2 */ unsigned char Version; /* 如果是ID3V2.3则保存3,如果是ID3V2.4则保存4 */ unsigned char Revision; /* 副版本号 */ unsigned char Flag; /* 标志,使用高三位,其它位为0 */ unsigned char Size[4]; /* 整个标签帧大小,除去本结构体的 10 个字节 */ #pragma pack() }; /* 标签帧帧头的结构 */ struct ID3V2Frame { #pragma pack(1) unsigned char id[4]; /* 标志帧,说明其内容,例如作者/标题等*/ char size[4]; /* 标志帧大小 */ char flags[2]; /* 标志帧,只定义了6位 */ #pragma pack() }
ID3V1:
/* ID3V1信息结构 */ struct ID3V1 { char Header[3]; /* 标签头"TAG",标识ID3V1 */ char Title[30]; /* 歌名 */ char Artist[30]; /* 作者 */ char Album[30]; /* 专辑 */ char Year[4]; /* 年份 */ char Comment[30]; /* 备注 */ char Genre; /* 类型 */ }
音频数据帧由帧头和音频数据组成:
struct DataFrameHeader { unsigned int bzFrameSyncFlag1:8; /* 全为 1 */ unsigned int bzProtectBit:1; /* CRC */ unsigned int bzVersionInfo:4; /* 包括 mpeg 版本,layer 版本 */ unsigned int bzFrameSyncFlag2:3; /* 全为 1 */ unsigned int bzPrivateBit:1; /* 私有 */ unsigned int bzPaddingBit:1; /* 是否填充,1 填充,0 不填充 layer1 是 4 字节,其余的都是 1 字节 */ unsigned int bzSampleIndex:2; /* 采样率索引 */ unsigned int bzBitRateIndex:4; /* bit 率索引 */ unsigned int bzExternBits:6; /* 版权等,不关心 */ unsigned int bzCahnnelMod:2; /* 通道 * 00 - Stereo 01 - Joint Stereo * 10 - Dual 11 - Single */ };
2、MPEG解码(以libmad-0.15.1b源代码为例)
libmad运行demo为minimad.c,从其中的用法可以看出需要用户设置几个回调函数已控制程序运行。分别为input, output,header,filter 和error 。其中input实现mp3文件输入并转换为mad_stream,output实现根据用户需求对数据的再处理。
使用libmad几个封装好的基本结构如下:
/* 存储未解码的比特流 */ struct mad_stream { unsigned char const *buffer; /* input bitstream buffer */ unsigned char const *bufend; /* end of buffer */ unsigned long skiplen; /* bytes to skip before next frame */ int sync; /* stream sync found */ unsigned long freerate; /* free bitrate (fixed) */ unsigned char const *this_frame; /* start of current frame */ unsigned char const *next_frame; /* start of next frame */ struct mad_bitptr ptr; /* current processing bit pointer */ struct mad_bitptr anc_ptr; /* ancillary bits pointer */ unsigned int anc_bitlen; /* number of ancillary bits */ unsigned char (*main_data)[MAD_BUFFER_MDLEN]; /* Layer III main_data() */ unsigned int md_len; /* bytes in main_data */ int options; /* decoding options (see below) */ enum mad_error error; /* error code (see above) */ }; /* 数据帧帧头 */ struct mad_header { enum mad_layer layer; /* audio layer (1, 2, or 3) */ enum mad_mode mode; /* channel mode (see above) */ int mode_extension; /* additional mode info */ enum mad_emphasis emphasis; /* de-emphasis to use (see above) */ unsigned long bitrate; /* stream bitrate (bps) */ unsigned int samplerate; /* sampling frequency (Hz) */ unsigned short crc_check; /* frame CRC accumulator */ unsigned short crc_target; /* final target CRC checksum */ int flags; /* flags (see below) */ int private_bits; /* private bits (see below) */ mad_timer_t duration; /* audio playing time of frame */ }; /* 有效数据帧 */ struct mad_frame { struct mad_header header; /* MPEG audio header */ int options; /* decoding options (from stream) */ mad_fixed_t sbsample[2][36][32]; /* synthesis subband filter samples */ mad_fixed_t (*overlap)[2][32][18]; /* Layer III block overlap data */ }; /* 采样后的PCM音频数据 */ struct mad_pcm { unsigned int samplerate; /* sampling frequency (Hz) */ unsigned short channels; /* number of channels */ unsigned short length; /* number of samples per channel */ mad_fixed_t samples[2][1152]; /* PCM output samples [ch][sample] */ }; /* 解码后的音频数据 */ struct mad_synth { mad_fixed_t filter[2][2][2][16][8]; /* polyphase filterbank outputs */ /* [ch][eo][peo][s][v] */ unsigned int phase; /* current processing phase */ struct mad_pcm pcm; /* PCM output */ }; /* 解码过程控制 */ enum mad_flow { MAD_FLOW_CONTINUE = 0x0000, /* continue normally */ MAD_FLOW_STOP = 0x0010, /* stop decoding normally */ MAD_FLOW_BREAK = 0x0011, /* stop decoding and signal an error */ MAD_FLOW_IGNORE = 0x0020 /* ignore the current frame */ };
libmad的示例程序流程如下,用户可以直接使用回调机制使用libmad,也可以使用decoder.c的接口按照需求获取中间数据。
五、OGG 文件 (参考:RFC 3533)
OGG(OGG Vobis)是一种新的音频压缩格式,类似于MP3等现有的音乐格式。完全免费,没有专利限制。Vorbis 是这种音频压缩机制的名字,OGG是一个计划的名字,该计划意图设计一个完全开放的多媒体系统,OGG Vorbis是该计划的一部分。
特点:支持多声道;现在创建的OGG文件可以在未来的任何播放器上播放,因此,这种文件格式可以不断地进行大小和音质的改良,而不影响既有的编码器和播放器;目前最好的有损格式之一,最高比特率500K。
1、相关概念
stream : 分为逻辑流与物理流,Ogg封装的结果称作物理流,一个物理流包含一个或多个逻辑流,这些逻辑流由ogg编码器创建,每个物理流中的逻辑流以一个特殊的页开始 -- bos,以一个特殊的页结束 -- eos;
physical bitstream with pages of different logical bitstreams grouped and chained ------------------------------------------------------------- |*A*|*B*|*C*|A|A|C|B|A|B|#A#|C|...|B|C|#B#|#C#|*D*|D|...|#D#| ------------------------------------------------------------- bos bos bos eos eos eos bos eos bos : beginning of stream; eos : end of stream 图中有两个物理流,第一个时序上有三个逻辑流ABC,分别以bos和eos为头尾;第二个只有一个逻辑流D
packet : 一个解码单元,或是一帧数据;
segment : 由 packet 分割而成,一个segment 最多包含255格式bytes,segment没有header ;
page : 是 ogg 文件格式的基本组成单元,是对 segment 的封装,为几个连续的 segment 添加 header 构成 page;2、音频格式剖析:
OGG文件基本构成单位是页(Page),编码过程是由物理流向页转换的过程,OGG编码的主要过程如下:
逻辑流由若干个packet组成 ----------------------------------------------------------------- > | packet_1 | packet_2 | packet_3 | < ----------------------------------------------------------------- |每个packet可以分成若干个segment v packet_1 (5 segments) packet_2 (4 segs) p_3 (2 segs) ------------------------------ -------------------- ------------ .. |seg_1|seg_2|seg_3|seg_4|s_5 | |seg_1|seg_2|seg_3|| |seg_1|s_2 | .. ------------------------------ -------------------- ------------ | 给连续的几个segment加上page header就组成了page v page_1 (packet_1 data) page_2 (pket_1 data) page_3 (packet_2 data) ------------------------ ---------------- ------------------------ |H|------------------- | |H|----------- | |H|------------------- | |D||seg_1|seg_2|seg_3| | |D|seg_4|s_5 | | |D||seg_1|seg_2|seg_3| | ... |R|------------------- | |R|----------- | |R|------------------- | ------------------------ ---------------- ------------------------ | pages of | other --------| | logical ------- bitstreams | MUX | ------- | v page_1 page_2 page_3 ------ ------ ------- ----- ------- ... || | || | || | || | || | ... ------ ------ ------- ----- ------- physical Ogg bitstream
1)从编码器获得逻辑流的各个packet;
2)将packet分割成Segment,分片;
3)将Segment 打包成Page,页封装;
4)将多个已经封装 page 完毕的逻辑流按应用要求的时序关系合成物理流,从而获得 OGG文件,对于只包含一个 stream 的文件,这个过程可以没有;3、OGG页结构
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| Byte +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | capture_pattern: Magic number for page start "OggS" | 0-3 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | version | header_type | granule_position | 4-7 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | 8-11 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | bitstream_serial_number | 12-15 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | page_sequence_number | 16-19 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | CRC_checksum | 20-23 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |page_segments | segment_table | 24-27 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ... | 28- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
1) 0~3字节:magic numbers,标识page开始;
2) version: 1字节;
3) page头类型:1字节:
位 0x01 : 1-该页包含前一页的连续的packet ;0- 该页和前后页无联系;
位 0x02 : 1- 该页为bos ; 0- 该页不是 bos;
位 0x04 : 1-该页为eos ; 0- 该页不是 eos ;
4) 位置信息: 8字节,对于音频
5) serial number: 4字节,唯一标识stream;
6) page sequence number: 4 字节,page序列号,为解码器提供页丢失标识,每个逻辑流中递增;
7) CRC校验和: 4字节;
8) number_page_segment : 1字节,segment table 中segment的个数;
9) segment table: 长度为 number_page_segment字节,每个字节对应一个segment的长度。
六、总结
总的来说,我了解了三种主流的音频文件格式,在以后的开发中打好了基础,但依然有一些不足,细节上不够深入,后续在接触后更新。libogg编解码ogg文件源码阅读进行中...
参考资料
1、 维基百科-WAV
2、 音频格式比较
3、 https://blog.csdn.net/ljxt523/article/details/52068241
4、 MP3:https://wenku.baidu.com/view/a071bf4e852458fb770b56a0.html
5、 OGG:https://blog.csdn.net/yu_yuan_1314/article/details/16884313
-
QAudioCoder:基于Qt的C ++库,用于音频解码,编码和转换。-开源
2021-05-12 12:55:18QAudioCoder是一个Qt库,用于解码和编码原始音频样本。 该库还提供直接解码或编码音频文件以及在不同格式之间转换的功能。 当前仅支持WAVE,MP3和FLAC格式。 -
Wav2MP3 Wizard:Wav2MP3向导是一个简单的日常音频编码/解码解决方案!-开源
2021-05-12 13:09:11Wav2MP3向导是日常音频编码/解码的简单解决方案。 它支持WAV,MP3,OGG和FLAC文件,并具有直观的界面。 新手易于学习(带有预置配置文件),并且具有足够的高级功能。 -
Python 技术篇-用base64库对音频、图片等文件进行base64编码和解码实例演示
2019-06-10 16:52:42下面演示我读取 file1 文件,进行编码,然后再解码,保存为另一个 file2 文件。最后的 file1 和 file2 是一样的。 图片、音频等文件都是二进制的文件,所以读取和写入要用 rb 和 wb,都多个 b。最近在研究项目,需要调用百度语音的api,传入参数需要本地语音文件 base64 位编码后内容。下面来演示一下。
其实很简单,base64 是系统自带的库。
base64.b64encode() 进行编码。
base64.b64decode() 进行解码。
下面演示我读取 file1 文件,进行编码,然后再解码,保存为另一个 file2 文件。最后的 file1 和 file2 是一样的。
图片、音频等文件都是二进制的文件,所以读取和写入要用 rb 和 wb,都多个 b。import base64 file1 = open("16k.pcm","rb").read() # 读取二进制文件 text = base64.b64encode(file1) # 进行编码 file2 = open("17k.pcm","wb") # 写入二进制文件 text = base64.b64decode(text) # 进行解码 file2.write(text) file2.close() # 写入文件完成后需要关闭文件才能成功写入
base64 编码使用实例演示:
Python 技术篇-百度语音识别API接口调用演示
音频文件 base64 位编码后的样子:
喜欢的点个赞❤吧! -
MPEG音频编解码器:从mp3到xHE-AAC
2021-01-20 07:07:26本文介绍了MPEG音频编解码器及其应用,并展现现代音频编码方案成功的创建者Fraunhofer IIS. MPEG L3: mp3 mp3彻底改变了音乐产业,也改变了消费者购买和享受音乐的方式。mp3目前仍然是音乐发行的主要格式,... -
音频编码解码简介
2014-05-15 14:46:00音频的编码和解码 1、处理音频数据的过程分为编码和解码两个过程,这两个过程的具体说明如下: (1)编码:把存放在波形文件里的数字音频数据转换为压缩的形式,...2、音频文件根据支持的编码格式,分为两类: (1)无 -
音频编码流程详解
2021-07-28 09:56:201、音频编码整体流程 2、FFmpeg音频编码详细流程 3、关键函数说明 (1)avcodec_find_encoder:根据指定的AVCodecID查找注册的编码器。 (2)avcodec_alloc_context3:为AVCodecContext分配内存。 (3)... -
[python] 对音频文件进行base64编码和解码
2019-03-08 22:06:47https://www.cnblogs.com/dcb3688/p/4610642.html