(HLS播放器之三)播放数据缓存机制之环形buffer

本文详细介绍了自定义LoopBuffer类的设计思路与实现细节,包括关键数据成员解释、核心读写函数解析,以及如何避免死循环等问题。

    Loop buffer, 主要是获取到的播放数据进行缓存。对于HLS播放器的播放数据的处理有比较多的处理模式,如映射文件、LoopBuffer、切片存储等。就要看具体需要了。

 

现在来看这是我自定的LoopBuffer类

class CLoopBuffer
{
private:
	CRITICAL_SECTION	m_mutex;
	__int64		m_iBufferSize;		/*当前数据管理缓冲区的大小 */
	__int64		m_iVaildLength;		/*当前数据缓冲区中有效数据长度*/

	unsigned char   *m_pBuffer;
	unsigned char	*m_pBufTail;
	unsigned char	*m_pCurRead;
	unsigned char	*m_pCurWrite;

	bool	m_bInterrupt;
	HANDLE	hMutex;
	bool	m_downloadEnd_flag;		
	bool	m_isRead;				// 阅读判断符
	bool	m_isStop;				// 强制终止标记

public:
	CLoopBuffer(__int64 iBufferSize);
	~CLoopBuffer();

	__int64	_buffer_read(unsigned char* pData, __int64 iDataLen, bool bForceEnough);
	void	_buffer_write(unsigned char* pData, __int64 iDataLen);
	void	_buffer_interrupt();
	void	_buffer_reset();
	void	_buffer_reset_readBegin();
	__int64 _buffer_getVaildLength();							//获取剩余量
	void	_buffer_setReadPos(__int64 pos);					        //设置播放位置
	__int64 _buffer_readLength();
	void	_buffer_stopRead();							        //停止读取工作
};

 

其中最重要就是标记位置的四个指针:

m_pBuffer:         缓存的头指针

m_pBufTail:       缓存的尾指针

m_pCurRead:    读取位置指针

m_pCurWrite:    写入位置指针

 

现在针对特殊的几个函数进行分析:

 

1、__int64 CLoopBuffer::_buffer_read(unsigned char* pData, __int64 iDataLen, bool bForceEnough)   读取LoopBuffer缓存数据函数

入参分析:

unsigned char* pData :待填充的Buffer

_int64 iDataLen :         需要存储的数据大小

bool  bForceEnough:  是否满填充,true 表示必须填充iDataLen大小的数据,false 反之

 

分析:

1、设定可提供的数据量。如果bForceEnough = true,则表示满数据量填充。不足则需要等待

2、拷贝数据到pData中。如果m_pCurRead到m_pBufTail的数据量小于可读取量,则需要取到尾部数据,和头部到剩余大小的数据。

注意: 等待数据足够时,小心死循环了。其中应该有强制退出标记。

 

__int64	CLoopBuffer::_buffer_read(unsigned char* pData, __int64 iDataLen, bool bForceEnough)
{
	if (NULL == m_pBuffer)
	{
		return 0;
	}

	__int64 iActualReadLen = iDataLen;
	__int64 iCurrToEndLen = 0;

	//1、得到需要提供的数据的长度
	if(true == bForceEnough)
	{
		//必须等待有足够的数据可读
		while(m_iVaildLength < iDataLen)
		{	
			......
			
			Sleep(50);
		}

	}
	else
	{
		// 能满足条件尽量满足条件,不满足条件时有多少数据读多少数据
		if(m_iVaildLength < iDataLen)
		{
			iActualReadLen = m_iVaildLength;
		}
	}
	WaitForSingleObject(hMutex, 1L); 
	
	//2、根据实际情况Copy数据
	//得到当前位置距离队列尾的长度
	iCurrToEndLen = (__int64)(m_pBufTail - m_pCurRead);

	if(iCurrToEndLen >= iActualReadLen)
	{
		//如果剩余长度足够,则直接copy并返回
		memcpy(pData,m_pCurRead, iActualReadLen);
		m_pCurRead += iActualReadLen;
	}
	else
	{
		//Step 1:有多少读多少
		memcpy(pData, m_pCurRead, iCurrToEndLen);

		//Step 2:移动到头部继续读剩余的数据	
		memcpy(pData+iCurrToEndLen, m_pBuffer, iActualReadLen-iCurrToEndLen);

		m_pCurRead = m_pBuffer + (iActualReadLen - iCurrToEndLen);
	}

	//3、减少当前Buf的长度
	EnterCriticalSection(&m_mutex);
	m_iVaildLength -= iActualReadLen;
	LeaveCriticalSection(&m_mutex);

	return iActualReadLen;
}


2、void CLoopBuffer::_buffer_write(unsigned char* pData, __int64 iDataLen)  写入数据到Loop buffer中

不细说了,和读取有些道理相通的。

void CLoopBuffer::_buffer_write(unsigned char* pData, __int64 iDataLen)
{
	__int64 iActualWriteLen = iDataLen;
	__int64 iCurrToEndLen = 0;

	//判断写入数据长度有效性
	if(iActualWriteLen > m_iBufferSize)
	{
		return;
	}

	//等待有足够空间可用于写数据
	while( (m_iBufferSize - m_iVaildLength) < iDataLen)
	{
		.....

		Sleep(100);
	}

	//将数据Copy到Buffer中
	iCurrToEndLen = (__int64)(m_pBufTail - m_pCurWrite); 
	if(iCurrToEndLen >= iDataLen )
	{
		//如果剩余长度足够,则直接copy并返回
		memcpy(m_pCurWrite, pData, iDataLen);
		m_pCurWrite += iDataLen;
	}
	else
	{
		//	Step 1:能填充多少数据先填充多少数据
		memcpy(m_pCurWrite, pData, iCurrToEndLen);

		//	Step 2:移动到头部继续填充剩余的数据	
		memcpy(m_pBuffer, pData+iCurrToEndLen, iDataLen-iCurrToEndLen);
		m_pCurWrite = m_pBuffer + (iDataLen-iCurrToEndLen);
	}

	//增加当前buf的长度
	EnterCriticalSection(&m_mutex);
	m_iVaildLength += iDataLen;
	LeaveCriticalSection(&m_mutex);
}


其他的函数只要起到控制功能、清空数据等处理。

 

大家一定要注意别出现死循环了。其中有一些While循环,特别关照它呀!

 

 


 

 

LOCAL void reset_audio_buffer(int session_id, int session_type) { for (int i = g_audio_play_params_loop.start_index; i <= g_audio_play_params_loop.end_index; i++) { if (session_id == g_audio_play_params_loop.session_id[i] && session_type == g_audio_play_params_loop.session_type[i]) { g_audio_play_params_loop.discard[i] = 1; } } } /** * @brief Checks if any audio channel has enough data for processing * @param frame_size Required size in bytes to check against * @return TRUE if at least one channel has more data than frame_size, FALSE otherwise */ LOCAL int has_enough_data(size_t frame_size) { if (NULL == g_mix_buffer) { return FALSE; } for (int ch = 0; ch < MIX_BUF_CH; ch++) { if (g_mix_buffer->mix_channels[ch].valid_len > frame_size) { return TRUE; } } return FALSE; } /** * @brief Mixes audio data from multiple channels into a single output stream * * This function reads audio data from multiple channels in a ring buffer, * mixes them together, and writes the mixed PCM data to the output. * After reading, the source buffer is cleared. * * @param frame_size Size of each audio frame in bytes * @param read_data Buffer to store data read from channels * @param mixed_data Buffer to store final mixed audio data * * @note Assumes g_mix_buffer is properly initialized with MIX_BUF_CH channels */ LOCAL void mix_audio_ex(size_t frame_size, unsigned char *read_data, unsigned char *mixed_data) { unsigned char *src_data[MIX_BUF_CH] = {NULL}; int ch = 0, i = 0; size_t start_pos = 0, read_len = 0, buffer_index = 0, valid_cnt = 0; memset(read_data, 0, frame_size * MIX_BUF_CH); // 从所有通道读取数据 for (ch = 0; ch < MIX_BUF_CH; ch++) { start_pos = g_mix_buffer->mix_channels[ch].read_pos; if (g_mix_buffer->mix_channels[ch].valid_len > 0) { valid_cnt++; // 从环形缓冲区复制数据 read_len = min(g_mix_buffer->mix_channels[ch].valid_len, frame_size); for (i = 0; i < read_len; i++) { buffer_index = (start_pos + i) % g_mix_buffer->buf_size; read_data[i + ch * frame_size] = g_mix_buffer->buffer[ch][buffer_index]; g_mix_buffer->buffer[ch][buffer_index] = 0; // 清空已读取数据 } // 更新缓冲区状态 g_mix_buffer->mix_channels[ch].read_pos = (start_pos + read_len) % g_mix_buffer->buf_size; g_mix_buffer->mix_channels[ch].valid_len -= read_len; } src_data[ch] = read_data + ch * frame_size; } // 环形内存中有数据的channel数量小于反向音频流的数量属于异常现象 if (valid_cnt < g_audio_stream_count) { SPEAKER_DEBUG("Audio stream with missing segments!"); } // 混合并写入PCM数据 mix_pcm_sound(src_data, MIX_BUF_CH, mixed_data, frame_size); write_PCM_sound((uint8_t*)mixed_data, frame_size, frame_size); } /** * @brief Audio mixing thread function that combines multiple audio streams * @param param Thread parameter (unused) * * Continuously reads audio data from input streams, mixes them together, * and processes the mixed output. The function allocates buffers for reading * and mixing audio frames, processes data while audio streams are active, * and cleans up resources before exiting. * * @return NULL */ static void *mix_audio(void *param) { size_t frame_size = g_chunk_byte * 2; unsigned char *read_data = calloc(frame_size * MIX_BUF_CH, sizeof(unsigned char)); unsigned char *mixed_data = calloc(frame_size, sizeof(unsigned char)); int32_t time_interval = 1000 * frame_size / (g_audio_play_params_loop.spk_sample_rate * AUDIO_PRE_FRAME_BIT / 8); if (!read_data || !mixed_data) { SPEAKER_ERROR("fail to alloc memory for read_data or mixed_data!"); if (read_data) free(read_data); if (mixed_data) free(mixed_data); return NULL; } while (0 < g_audio_stream_count) { if (TRUE == has_enough_data(frame_size)) { mix_audio_ex(frame_size, read_data, mixed_data); } } /* 当所有用户关闭双讲后,播放环形缓冲区剩余音频,至多播放一圈 */ unsigned long long int start_time = nvmp_get_us() / 1000; unsigned long long int now_time = start_time; while (TRUE == has_enough_data(frame_size)) { mix_audio_ex(frame_size, read_data, mixed_data); now_time = nvmp_get_us() / 1000; if (now_time - start_time > MIX_BUF_BL * time_interval) { break; } } g_audio_stream_context.mix_active = 0; free(read_data); free(mixed_data); return NULL; } /** * @brief Initialize the audio mixing thread and buffer * * Initializes a mixing buffer for audio channels if not already created. * Creates a thread for audio mixing operations. * The buffer can store 5 maximum-sized audio frames per channel. * * @return OK on success, ERROR on failure */ LOCAL int init_mix_audio_thread() { if (NULL == g_mix_buffer) { g_mix_buffer = init_mix_buf(MIX_BUF_CH, g_chunk_byte * 2 * MIX_BUF_BL); //每个音频通道可以存放MIX_BUF_BL个SDK层能够播放的最大音频帧 if (NULL == g_mix_buffer) { SPEAKER_ERROR("fail to init mix buffer\n"); return ERROR; } } g_audio_stream_count = 1; g_audio_stream_context.mix_active = 1; if (0 != pthread_create(&(g_audio_stream_context.mix_pid), NULL, mix_audio, NULL)) { SPEAKER_ERROR("fail to init mix audio thread\n"); g_audio_stream_count = 0; g_audio_stream_context.mix_active = 0; return ERROR; } else { pthread_detach(g_audio_stream_context.mix_pid); } return OK; }解析代码,这些可以用在我录像的混音中吗?
最新发布
10-17
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会唱歌的老樊

老少爷们,来个赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值