利用libmad解码mp3

mp3是流媒体,所以一个完整文件往往比较大而且不能一次装入sound缓存,所以其buffer管理就成了最大难题,至于解码部分其实还是很简单的,下面是仅关于解码部分的说明

首先应该在自己的工程中包含以下三个库:

  • libid3tag-0.15.1b
  • libmad-0.15.1b
  • libz-1.1.4

必要的三个结构体及相关的初始化

#include <mad.h>
#include <id3tag.h>

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管理机制:

详细代码可以参考我写的一个类:

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;
}


 
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值