利用libmad解码mp3
2010-07-05 13:19
218 查看
mp3是流媒体,所以一个完整文件往往比较大而且不能一次装入sound缓存,所以其buffer管理就成了最大难题,至于解码部分其实还是很简单的,下面是仅关于解码部分的说明
首先应该在自己的工程中包含以下三个库:
libid3tag-0.15.1b
libmad-0.15.1b
libz-1.1.4
必要的三个结构体及相关的初始化
将stream和buffer关联起来
对压缩数据进行解码,这里将控制着一个双层的循环逻辑
如果mad_frame_decode返回-1,这说明这里存在错误这需要对特定的错误进行处理
当错误代码为!MAD_RECOVERABLE(stream.error),则需要从新更新流媒体buffer
当错误代码为MAD_ERROR_BADDATAPTR时需要再进行decode
当错误代码为MAD_ERROR_LOSTSYNC则需要进行id3tag跳帧,并再次decode
将解码数据转换为设备所能接受的pcm数据
但是即使是pcm数据依然不能直接播放,还需要更具设备的支持能力进行相应的线性插值,转换成8位、16位、及32位……双声道的转换方式
单声道的转换方式
当这个转换后的pcm数据积累到一定程度就可以拷贝到设备缓存区进行播放了这里省略不同设备的创建及播放代码,要注意的是创建设备时需要的音频信息是从synth.pcm中获得的,如channels,samplerate,……
最后不要忘记如何关闭相关资源
再举例一个基于looping结构的direct sound buffer管理机制:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/06/e932c088a7150d93947421a8fcbb1bb2.gif)
详细代码可以参考我写的一个类:
首先应该在自己的工程中包含以下三个库:
libid3tag-0.15.1b
libmad-0.15.1b
libz-1.1.4
必要的三个结构体及相关的初始化
mad_stream stream; mad_frame frame; mad_synth synth; mad_stream_init(&stream); mad_frame_init(&frame); mad_synth_init(&synth);
将stream和buffer关联起来
mad_stream_buffer(&stream, buffer, buffer_size);
对压缩数据进行解码,这里将控制着一个双层的循环逻辑
mad_frame_decode(&frame, &stream);
如果mad_frame_decode返回-1,这说明这里存在错误这需要对特定的错误进行处理
当错误代码为!MAD_RECOVERABLE(stream.error),则需要从新更新流媒体buffer
当错误代码为MAD_ERROR_BADDATAPTR时需要再进行decode
当错误代码为MAD_ERROR_LOSTSYNC则需要进行id3tag跳帧,并再次decode
id3_tag_query(stream.this_frame, stream.bufend - stream.this_frame); if(tagsize > 0) { mad_stream_skip(&stream, tagsize); }
将解码数据转换为设备所能接受的pcm数据
mad_synth_frame(&synth, &frame);
但是即使是pcm数据依然不能直接播放,还需要更具设备的支持能力进行相应的线性插值,转换成8位、16位、及32位……双声道的转换方式
for(int i = 0; i < (int)synth.pcm.length; i++) { sample0 = audio_linear_dither(16, synth.pcm.samples[0][i], &left_dither, &stats); sample1 = audio_linear_dither(16, synth.pcm.samples[1][i], &right_dither, &stats); tempBuffer[0] = sample0 >> 0; tempBuffer[1] = sample0 >> 8; tempBuffer[2] = sample1 >> 0; tempBuffer[3] = sample1 >> 8; }
单声道的转换方式
register int sample0; for(int i = 0; i < (int)synth.pcm.length; i++) { sample0 = audio_linear_dither(16, synth.pcm.samples[0][i], &left_dither, &stats); tempBuffer[0] = sample0 >> 0; tempBuffer[1] = sample0 >> 8; }
当这个转换后的pcm数据积累到一定程度就可以拷贝到设备缓存区进行播放了这里省略不同设备的创建及播放代码,要注意的是创建设备时需要的音频信息是从synth.pcm中获得的,如channels,samplerate,……
最后不要忘记如何关闭相关资源
mad_synth_finish(&synth); mad_frame_finish(&frame); mad_stream_finish(&stream);
再举例一个基于looping结构的direct sound buffer管理机制:
![](https://oscdn.geek-share.com/Uploads/Images/Content/202004/06/e932c088a7150d93947421a8fcbb1bb2.gif)
详细代码可以参考我写的一个类:
class Mp3 : public ::my::Thread { protected: static const DWORD MPEG_BUFSZ = 40000; static const DWORD BLOCK_COUNT = 3; protected: t3d::DSoundPtr m_dsound; WAVEFORMATEX m_wavfmt; t3d::DSBufferPtr m_dsbuffer; t3d::DSNotifyPtr m_dsnotify; DSBPOSITIONNOTIFY m_dsnp[BLOCK_COUNT]; Event m_events[BLOCK_COUNT + 1]; IOStreamPtr m_stream; DWORD m_flags; typedef std::vector<unsigned char> FileBuffer; FileBuffer m_buffer; bool m_loop; CriticalSection m_loopLock; void setLoop(bool loop) { CriticalSectionLock lock(m_loopLock); m_loop = loop; } bool getLoop(void) { CriticalSectionLock lock(m_loopLock); return m_loop; } public: Mp3( t3d::DSoundPtr dsound, IOStreamPtr fstream, DWORD flags = DSBCAPS_CTRLVOLUME | DSBCAPS_STATIC | DSBCAPS_LOCSOFTWARE); virtual ~Mp3(void); protected: bool playOnce(void); public: void play(bool loop = false); void stop(void); DWORD onProc(void); };
Mp3::Mp3(
t3d::DSoundPtr dsound,
IOStreamPtr stream,
DWORD flags /*= DSBCAPS_CTRLVOLUME | DSBCAPS_STATIC | DSBCAPS_LOCSOFTWARE*/)
: m_dsound(dsound)
, m_stream(stream)
, m_flags(flags)
, m_buffer(MPEG_BUFSZ / sizeof(m_buffer[0]))
{
m_wavfmt.wFormatTag = WAVE_FORMAT_PCM;
m_wavfmt.nChannels = 0;
m_wavfmt.nSamplesPerSec = 0;
// 注意:由于优先级关系,设定第一个事件为停止事件,后面依次作为 m_dsnp 的事件
for(int i = 0; i < _countof(m_dsnp); i++)
{
m_dsnp[i].dwOffset = 0;
m_dsnp[i].hEventNotify = m_events[i + 1].m_hevent;
}
}
Mp3::~Mp3(void)
{
if(NULL != m_hThread)
{
stop();
VERIFY(WaitForThreadStopped(INFINITE));
}
}
bool Mp3::playOnce(void)
{
DSBUFFERDESC dsbd;
audio_dither left_dither, right_dither;
std::vector<unsigned char> sbuffer;
m_stream->seek(0, my::IOStream::seek_set);
mad_stream stream;
mad_frame frame;
mad_synth synth;
mad_stream_init(&stream);
mad_frame_init(&frame);
mad_synth_init(&synth);
// 初始化所有 notify
for(size_t i = 0; i < _countof(m_dsnp); i++)
{
VERIFY(::ResetEvent(m_dsnp[i].hEventNotify));
}
// 设置默认已经开始播放的 block
VERIFY(::SetEvent(m_dsnp[0].hEventNotify));
bool ret = false;
do
{
// 从 stream 获得原始声音 buffer
size_t remain = 0;
if(NULL != stream.next_frame)
{
remain = &m_buffer[0] + MPEG_BUFSZ - stream.next_frame;
memmove(&m_buffer[0], stream.next_frame, remain);
}
int read = m_stream->read(&m_buffer[0] + remain, sizeof(m_buffer[0]), m_buffer.size() - remain);
// 如果已经到达 stream 结尾,则结束
if(0 == read)
{
if(NULL != m_dsbuffer)
{
// 等待缓存区播完
_ASSERT(sizeof(m_events) == sizeof(HANDLE) * _countof(m_events));
if(WAIT_OBJECT_0 != ::WaitForMultipleObjects(_countof(m_events), reinterpret_cast<HANDLE *>(m_events), FALSE, INFINITE))
{
// 正常播放完成,要求继续播放
ret = true;
}
m_dsbuffer->stop();
}
goto end;
}
// 如果读出来的 buffer 太小,则将 MAD_BUFFER_GUARD 的剩余 buffer 清零
if(read < MAD_BUFFER_GUARD)
{
_ASSERT(MPEG_BUFSZ - remain > MAD_BUFFER_GUARD);
memset(&m_buffer[remain + read], 0, MAD_BUFFER_GUARD - read);
read = MAD_BUFFER_GUARD;
}
// attach buffer to mad stream
mad_stream_buffer(&stream, &m_buffer[0], (remain + read) * sizeof(m_buffer[0]));
while(true)
{
// decode audio frame
if(-1 == mad_frame_decode(&frame, &stream))
{
if(!MAD_RECOVERABLE(stream.error))
{
break;
}
switch(stream.error)
{
case MAD_ERROR_BADDATAPTR:
continue;
case MAD_ERROR_LOSTSYNC:
{
// excute id3 tag frame skipping
unsigned long tagsize = id3_tag_query(stream.this_frame, stream.bufend - stream.this_frame); if(tagsize > 0) { mad_stream_skip(&stream, tagsize); }}
continue;
default:
continue;
}
}
// convert frame data to pcm data
mad_synth_frame(&synth, &frame);
// parse dither linear pcm data to compatible format
audio_stats stats;
if(2 == synth.pcm.channels)
{
register signed int sample0, sample1;
for(int i = 0; i < (int)synth.pcm.length; i++)
{
sample0 = audio_linear_dither(16, synth.pcm.samples[0][i], &left_dither, &stats);
sample1 = audio_linear_dither(16, synth.pcm.samples[1][i], &right_dither, &stats);
sbuffer.push_back(sample0 >> 0);
sbuffer.push_back(sample0 >> 8);
sbuffer.push_back(sample1 >> 0);
sbuffer.push_back(sample1 >> 8);
}
}
else
{
register int sample0;
for(int i = 0; i < (int)synth.pcm.length; i++)
{
sample0 = audio_linear_dither(16, synth.pcm.samples[0][i], &left_dither, &stats);
sbuffer.push_back(sample0 >> 0);
sbuffer.push_back(sample0 >> 8);
}
}
// 必要时创建 dsound buffer
if(m_wavfmt.nChannels != synth.pcm.channels || m_wavfmt.nSamplesPerSec != synth.pcm.samplerate)
{
// dsound buffer 应该只被创建一次
_ASSERT(NULL == m_dsnotify);
_ASSERT(NULL == m_dsbuffer);
_ASSERT(WAVE_FORMAT_PCM == m_wavfmt.wFormatTag);
m_wavfmt.nChannels = synth.pcm.channels;
m_wavfmt.nSamplesPerSec = synth.pcm.samplerate;
m_wavfmt.wBitsPerSample = 16;
m_wavfmt.nBlockAlign = m_wavfmt.nChannels * m_wavfmt.wBitsPerSample / 8;
m_wavfmt.nAvgBytesPerSec = m_wavfmt.nSamplesPerSec * m_wavfmt.nBlockAlign;
m_wavfmt.cbSize = 0;
dsbd.dwSize = sizeof(dsbd);
dsbd.dwFlags = m_flags | DSBCAPS_CTRLPOSITIONNOTIFY;
dsbd.dwBufferBytes = m_wavfmt.nAvgBytesPerSec * BLOCK_COUNT;
dsbd.dwReserved = 0;
dsbd.lpwfxFormat = &m_wavfmt;
dsbd.guid3DAlgorithm = DS3DALG_DEFAULT;
// 重新计算每个块的播放 position
for(int i = 0; i < _countof(m_dsnp); i++)
{
m_dsnp[i].dwOffset = i * m_wavfmt.nAvgBytesPerSec;
}
// 创建 dsound buffer 及 dsound notify
m_dsbuffer = m_dsound->createSoundBuffer(&dsbd);
m_dsnotify = m_dsbuffer->getDSNotify();
m_dsnotify->setNotificationPositions(_countof(m_dsnp), m_dsnp);
}
// fill pcm data to dsbuffer
_ASSERT(NULL != m_dsbuffer);
if(sbuffer.size() > m_wavfmt.nAvgBytesPerSec)
{
// 等待所有事件处理
_ASSERT(sizeof(m_events) == sizeof(HANDLE) * _countof(m_events));
DWORD wait_res = ::WaitForMultipleObjects(_countof(m_events), reinterpret_cast<HANDLE *>(m_events), FALSE, INFINITE);
_ASSERT(WAIT_TIMEOUT != wait_res);
if(wait_res == WAIT_OBJECT_0)
{
// 是停止事件,则直接 out
m_dsbuffer->stop();
goto end;
}
// 计算当前 block
DWORD curr_block = wait_res - WAIT_OBJECT_0 - 1;
_ASSERT(curr_block < _countof(m_dsnp));
// 计算需要更新 block(curr_block + 1)
DWORD next_block = (curr_block + 1) % _countof(m_dsnp);
// 拷贝数据缓存
unsigned char * audioPtr1, * audioPtr2;
DWORD audioBytes1, audioBytes2;
m_dsbuffer->lock(m_dsnp[next_block].dwOffset, m_wavfmt.nAvgBytesPerSec, (LPVOID *)&audioPtr1, &audioBytes1, (LPVOID *)&audioPtr2, &audioBytes2, 0);
_ASSERT(audioBytes1 + audioBytes2 <= m_wavfmt.nAvgBytesPerSec);
if(audioPtr1 != NULL)
{
memcpy(audioPtr1, &sbuffer[0], audioBytes1);
}
if(audioPtr2 != NULL)
{
memcpy(audioPtr2, &sbuffer[0 + audioBytes1], audioBytes2);
}
m_dsbuffer->unlock(audioPtr1, audioBytes1, audioPtr2, audioBytes2);
// 开始播放
if(!m_dsbuffer->isPlaying())
{
// 重新设置当前初播放位置
m_dsbuffer->setCurrentPosition(m_dsnp[next_block].dwOffset);
m_dsbuffer->play(0, DSBPLAY_LOOPING);
}
// 将剩余 buffer 移动到 buffer 头
size_t remain = sbuffer.size() - m_wavfmt.nAvgBytesPerSec;
memmove(&sbuffer[0], &sbuffer[m_wavfmt.nAvgBytesPerSec], remain);
sbuffer.resize(remain);
}
}
}
while(stream.error == MAD_ERROR_BUFLEN);
_ASSERT(false);
end:
mad_synth_finish(&synth); mad_frame_finish(&frame); mad_stream_finish(&stream);return ret;
}
void Mp3::play(bool loop /*= false*/)
{
if(NULL != m_hThread)
{
if(!WaitForThreadStopped(0))
{
return;
}
VERIFY(::CloseHandle(m_hThread));
m_hThread = NULL;
}
setLoop(loop);
m_events[0].ResetEvent();
CreateThread();
ResumeThread();
}
void Mp3::stop(void)
{
m_events[0].SetEvent();
}
DWORD Mp3::onProc(void)
{
try
{
do
{
}
while(playOnce() && getLoop());
}
catch(t3d::Exception & /*e*/)
{
//::my::Game::getSingleton().m_pwnd->sendMessage(WM_USER + 0, (WPARAM)&e);
}
return 0;
}
相关文章推荐
- 利用libmad解码mp3
- 关于libmad开源mp3音频解码库的使用
- libmad解码mp3生成pcm文件
- MyMinimad ── Linux下用libmad写的mp3解码播放程序
- libmad解码MP3——/dev/dsp播放
- 基于libmad库的MP3解码简析
- libmad解码MP3——/dev/dsp播放
- 开源mp3解码库libmad编译及minimad使用
- linux下使用libmad库实现mp3文件的解码、播放
- 基于libmad编写mp3解码程序
- MyMinimad ── Linux下用libmad写的mp3解码播放程序(二)
- libmad和libmpg123解码mp3在安卓手机上的比较
- libmad学习进阶2----利用libmad将mp3转码成pcm
- libmad MP3解码
- 基于libmad的MP3解码播放器
- 让终端支持播放mp3,移植mp3解码库libmad和madplay到嵌入式linux
- 使用LibMad解码MP3,Windows上播放MP3,MP3转WAV实例代码
- MyMinimad ── Linux下用libmad写的mp3解码播放程序
- libmad学习进阶2----利用libmad将mp3转码成pcm
- linux 基于alsa 使用libmad 解码库实现MP3文件的播放