windows api录音pcm

基于讯飞音频api修改

#ifndef __WINREC_H__
#define __WINREC_H__

#include <cstdint>
#include <Windows.h>

namespace WinRecHZ
{
	/* Do not change the sequence */
	enum {
		RECORD_STATE_CREATED,	/* Init		*/
		RECORD_STATE_READY,		/* Opened	*/
		RECORD_STATE_STOPPING,	/* During Stop	*/
		RECORD_STATE_RECORDING,	/* Started	*/
	};
	/* error code */
	enum {
		RECORD_ERR_BASE = 0,
		RECORD_ERR_GENERAL,
		RECORD_ERR_MEMFAIL,
		RECORD_ERR_INVAL,
		RECORD_ERR_NOT_READY
	};

#define SAMPLE_RATE  16000
#define SAMPLE_BIT_SIZE 16
#define CHANNEL_COUNT 1
#define FRAME_COUNT   10
#define BUF_COUNT   4

#define VERIFY_FILE_NAME	"rec.pcm"  //rec.wav

	struct WavPCMFileHeader
	{
		struct RIFF
		{
			const    char rift[4] = { 'R','I', 'F', 'F' };
			uint32_t fileLength;
			const    char wave[4] = { 'W','A', 'V', 'E' };
		}riff;

		struct Format
		{
			const    char fmt[4] = { 'f','m', 't', ' ' };
			uint32_t blockSize = 16;
			uint16_t formatTag;
			uint16_t channels;
			uint32_t samplesPerSec;
			uint32_t avgBytesPerSec;
			uint16_t blockAlign;
			uint16_t  bitsPerSample;
		}format;

		struct  Data
		{
			const    char data[4] = { 'd','a', 't', 'a' };
			uint32_t dataLength;
		}data;

		WavPCMFileHeader(int dataSize)
		{
			riff.fileLength = 36 + dataSize;
			format.formatTag = WAVE_FORMAT_PCM;
			format.channels = CHANNEL_COUNT;
			format.samplesPerSec = SAMPLE_RATE;
			format.avgBytesPerSec = SAMPLE_RATE * CHANNEL_COUNT * SAMPLE_BIT_SIZE / 8;
			format.blockAlign = CHANNEL_COUNT * SAMPLE_BIT_SIZE / 8;
			format.bitsPerSample = SAMPLE_BIT_SIZE;
			data.dataLength = dataSize;
		}
	};

	/* recorder object. */
	struct recorder {
		volatile int state;		/* internal record state */
		void* wavein_hdl;
		void* rec_thread_hdl;
		void* bufheader;
		unsigned int bufcount;
	};

	class winrec
	{
	public:
		winrec();
		~winrec();

		int startRecord(); // Return 0 in success, otherwise return error code.

		int stopRecord();  // Return 0 in success, otherwise return error code.

		bool isRecordStopped();

		static DWORD m_iTotalDataLength;

	private:

		recorder* m_rec;

		int open_rec_device(WAVEFORMATEX* format, HANDLE thread, HWAVEIN* wave_hdl_out);

		int prepare_rec_buffer(HWAVEIN wi, WAVEHDR** bufheader_out, unsigned int headercount, unsigned int bufsize);

		void free_rec_buffer(HWAVEIN wi, WAVEHDR* first_header, unsigned headercount);

		void goto_fail();

		int open_recorder(WAVEFORMATEX* fmt);

		void close_recorder();

		int create_callback_thread(void *thread_proc_para, HANDLE *thread_hdl_out);

		void close_callback_thread(HANDLE thread);

		int start_record_internal(HWAVEIN wi, WAVEHDR* header, unsigned int bufcount);

		int open_recorder_internal(WAVEFORMATEX * fmt);
	};
}
#endif

#include <cstdlib>
#include <cstdio>
#include <process.h>
#include "winrec.h"

#pragma comment(lib, "winmm.lib")

namespace WinRecHZ
{
	DWORD winrec::m_iTotalDataLength = 0;

	static HANDLE msgqueue_ready_evt = NULL; /* signaled: the message queque has been created in the thread */

	static FILE* fdwav = NULL;

	static void data_proc(recorder *rec, MSG *msg);
	static unsigned int  __stdcall record_thread_proc(void * para);
	static void write_to_file(char* data, size_t length);

	winrec::winrec()
	{
		m_rec = (recorder*)malloc(sizeof(recorder));
		if (!m_rec)		return;

		memset(m_rec, 0, sizeof(struct recorder));
		m_rec->state = RECORD_STATE_CREATED;

		// 音频格式,声道个数,采样率,传输速率,块对齐大小,采样精度,额外空间
		WAVEFORMATEX wavfmt = { WAVE_FORMAT_PCM, CHANNEL_COUNT, SAMPLE_RATE, SAMPLE_RATE * CHANNEL_COUNT * SAMPLE_BIT_SIZE / 8,
			CHANNEL_COUNT * SAMPLE_BIT_SIZE / 8, SAMPLE_BIT_SIZE, sizeof(WAVEFORMATEX) };

		open_recorder(&wavfmt);
	}

	winrec::~winrec()
	{

	}

	int winrec::create_callback_thread(void *thread_proc_para, HANDLE *thread_hdl_out)
	{
		int ret = 0;

		HANDLE rec_thread_hdl = 0;
		unsigned int rec_thread_id;

		msgqueue_ready_evt = CreateEvent(NULL, TRUE, FALSE, NULL);
		if (msgqueue_ready_evt == NULL)
			return -1;

		rec_thread_hdl = (HANDLE)_beginthreadex(NULL, 0, record_thread_proc, thread_proc_para, 0, &rec_thread_id);
		if (rec_thread_hdl == 0) {
			CloseHandle(msgqueue_ready_evt);
			msgqueue_ready_evt = NULL;

			return -1;
		}

		*thread_hdl_out = rec_thread_hdl;

		/* wait the message queue of the new thread has been created */
		WaitForSingleObject(msgqueue_ready_evt, INFINITE);

		return 0;
	}

	void winrec::close_callback_thread(HANDLE thread)
	{
		if (thread == NULL)
			return;

		if (msgqueue_ready_evt) {
			/* if quit before the thread ready */
			WaitForSingleObject(msgqueue_ready_evt, INFINITE);
			CloseHandle(msgqueue_ready_evt);
			msgqueue_ready_evt = NULL;

			PostThreadMessage(GetThreadId(thread), WM_QUIT, 0, 0);
			WaitForSingleObject(thread, INFINITE);
			CloseHandle(thread);
		}
	}

	/* the recording callback thread procedure */
	static unsigned int  __stdcall record_thread_proc(void * para)
	{
		MSG msg;
		BOOL bRet;
		recorder *rec = (recorder *)para;

		/* trigger the message queue generator */
		PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
		SetEvent(msgqueue_ready_evt);

		while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
			if (bRet == -1) {
				continue;
			}

			switch (msg.message) {
			case MM_WIM_OPEN:
				printf("opened....\n");
				break;
			case MM_WIM_CLOSE:
				printf("closed....\n");
				PostQuitMessage(0);
				break;
			case MM_WIM_DATA:
				data_proc(rec, &msg);
				break;
			default:
				break;
			}
		}

		return 0;
	}

	static void data_proc(recorder *rec, MSG *msg)
	{
		HWAVEIN whdl;
		WAVEHDR *buf;

		whdl = (HWAVEIN)msg->wParam;
		buf = (WAVEHDR *)msg->lParam;

		winrec::m_iTotalDataLength += buf->dwBytesRecorded;

		//printf("data....\n");
		//printf("-----\n");
		//printf("Buf %x: User= %d, Len=%d, Rec=%d, Flag=%x\n", buf,
		//	buf->dwUser, buf->dwBufferLength, buf->dwBytesRecorded,
		//	buf->dwFlags);
		//printf("-----\n");


		/* dwUser should be index + 1 */
		if (buf->dwUser > rec->bufcount) {
			printf("data_proc: something wrong. maybe buffer is reset.\n");
			return;
		}

		write_to_file(buf->lpData, buf->dwBytesRecorded);

		switch (rec->state) {
		case RECORD_STATE_RECORDING:
			// after copied, put it into the queue of driver again.
			waveInAddBuffer(whdl, buf, sizeof(WAVEHDR));
			break;
		case RECORD_STATE_STOPPING:
		default:
			/* from this flag, can check if the whole data is processed after stopping */
			buf->dwUser = 0;
			break;
		}
	}

	int winrec::open_rec_device(WAVEFORMATEX* format, HANDLE thread, HWAVEIN* wave_hdl_out)
	{
		MMRESULT res;
		HWAVEIN wi = NULL;
		WAVEFORMATEX fmt;
		WAVEFORMATEX* final_fmt;

		if (thread == NULL)
			return -RECORD_ERR_INVAL;

		if (format == NULL) {
			fmt.wFormatTag = WAVE_FORMAT_PCM;
			fmt.nChannels = 1;
			fmt.nSamplesPerSec = SAMPLE_RATE;
			fmt.nAvgBytesPerSec = SAMPLE_RATE * 2;
			fmt.nBlockAlign = 2;
			fmt.wBitsPerSample = SAMPLE_BIT_SIZE;
			fmt.cbSize = sizeof(WAVEFORMATEX);
			final_fmt = &fmt;
		}
		else {
			final_fmt = format;
		}
		res = waveInOpen((LPHWAVEIN)&wi, WAVE_MAPPER, final_fmt, GetThreadId(thread), (DWORD_PTR)0, CALLBACK_THREAD);
		if (res != MMSYSERR_NOERROR) {
			return 0 - res;
		}

		*wave_hdl_out = wi;
		return 0;
	}

	int winrec::prepare_rec_buffer(HWAVEIN wi, WAVEHDR** bufheader_out, unsigned int headercount, unsigned int bufsize)
	{
		int ret = 0;
		unsigned int i = 0;
		WAVEHDR* header;
		MMRESULT res;

		/* at least double buffering */
		if (headercount < 2 || bufheader_out == NULL)
			return -RECORD_ERR_INVAL;

		header = (WAVEHDR*)malloc(sizeof(WAVEHDR) * headercount);
		if (!header)
			return -RECORD_ERR_MEMFAIL;
		memset(header, 0, sizeof(WAVEHDR) * headercount);

		for (i = 0; i < headercount; ++i) {
			(header + i)->lpData = (LPSTR)malloc(bufsize);
			if ((header + i)->lpData == NULL) {
				free_rec_buffer(wi, header, headercount);
			}
			(header + i)->dwBufferLength = bufsize;
			(header + i)->dwFlags = 0;
			(header + i)->dwUser = i + 1; /* my usage: if 0, indicate it's not used */

			res = waveInPrepareHeader(wi, header + i, sizeof(WAVEHDR));
			if (res != MMSYSERR_NOERROR) {
				free_rec_buffer(wi, header, headercount);
			}
		}

		*bufheader_out = header;
		return ret;
	}

	void winrec::free_rec_buffer(HWAVEIN wi, WAVEHDR* first_header, unsigned headercount)
	{
		unsigned int i;
		WAVEHDR* header;
		if (first_header == NULL || headercount == 0)
			return;

		header = first_header;
		for (i = 0; i < headercount; ++i) {
			if (header->lpData) {
				if (WHDR_PREPARED & header->dwFlags)
					waveInUnprepareHeader(wi, header, sizeof(WAVEHDR));
				free(header->lpData);
			}
			header++;
		}
		free(first_header);
	}

	int winrec::start_record_internal(HWAVEIN wi, WAVEHDR* header, unsigned int bufcount)
	{
		MMRESULT res;
		unsigned int i;

		if (bufcount < 2)
			return -1;

		/* must put at least one buffer into the driver first.
		and this buffer must has been allocated and prepared. */
		for (i = 0; i < bufcount; ++i) {
			if ((header->dwFlags & WHDR_INQUEUE) == 0) {
				header->dwUser = i + 1;
				res = waveInAddBuffer(wi, header, sizeof(WAVEHDR));
				if (res != MMSYSERR_NOERROR) {
					waveInReset(wi);
					return 0 - res;
				}
			}
			header++;
		}
		res = waveInStart(wi);
		if (MMSYSERR_NOERROR != res) {
			waveInReset(wi);
			return 0 - res;
		}

		return 0;
	}

	int winrec::open_recorder_internal(WAVEFORMATEX * fmt)
	{
		unsigned int buf_size;
		int ret = 0;

		m_rec->bufcount = BUF_COUNT;
		m_rec->wavein_hdl = NULL;
		m_rec->rec_thread_hdl = NULL;
		ret = create_callback_thread((void *)m_rec, &m_rec->rec_thread_hdl);
		if (ret != 0) {
			goto_fail();
			return ret;
		}

		ret = open_rec_device(fmt, (HANDLE)m_rec->rec_thread_hdl, (HWAVEIN *)&m_rec->wavein_hdl);
		if (ret != 0) {
			goto_fail();
			return ret;
		}

		if (fmt)
			buf_size = fmt->nBlockAlign *(fmt->nSamplesPerSec / 50) * FRAME_COUNT; // 200ms
		else
			buf_size = FRAME_COUNT * 20 * 16 * 2;  // 16khz, 16bit, 200ms;

		ret = prepare_rec_buffer((HWAVEIN)m_rec->wavein_hdl, (WAVEHDR **)&m_rec->bufheader, m_rec->bufcount, buf_size);
		if (ret != 0) {
			goto_fail();
			return ret;
		}

		return 0;
	}

	void winrec::goto_fail()
	{
		if (m_rec->bufheader) {
			free_rec_buffer((HWAVEIN)m_rec->wavein_hdl, (WAVEHDR *)m_rec->bufheader, m_rec->bufcount);
			m_rec->bufheader = NULL;
			m_rec->bufcount = 0;
		}
		if (m_rec->wavein_hdl != NULL) {
			waveInClose((HWAVEIN)m_rec->wavein_hdl);
			m_rec->wavein_hdl = NULL;
		}
		if (m_rec->rec_thread_hdl) {
			close_callback_thread(m_rec->rec_thread_hdl);
			m_rec->rec_thread_hdl = NULL;
		}
	}

	int winrec::open_recorder(WAVEFORMATEX* fmt)
	{
		int ret = 0;
		if (!m_rec)
			return -RECORD_ERR_INVAL;
		if (m_rec->state >= RECORD_STATE_READY)
			return 0;

		ret = open_recorder_internal(fmt);
		if (ret == 0)
			m_rec->state = RECORD_STATE_READY;
		return 0;
	}

	void winrec::close_recorder()
	{
		if (m_rec == NULL || m_rec->state < RECORD_STATE_READY)
			return;
		if (m_rec->state == RECORD_STATE_RECORDING)
			stopRecord();

		if (m_rec->wavein_hdl != NULL) {
			waveInClose((HWAVEIN)m_rec->wavein_hdl);
			if (m_rec->rec_thread_hdl) {
				close_callback_thread((HANDLE)m_rec->rec_thread_hdl);
				m_rec->rec_thread_hdl = NULL;
			}
			if (m_rec->bufheader) {
				free_rec_buffer((HWAVEIN)m_rec->wavein_hdl, (WAVEHDR*)m_rec->bufheader, m_rec->bufcount);
				m_rec->bufheader = NULL;
				m_rec->bufcount = 0;
			}
			m_rec->wavein_hdl = NULL;
		}

		m_rec->state = RECORD_STATE_CREATED;

		if (!m_rec)
			return;
		free(m_rec);
	}

	int winrec::startRecord()
	{
		int ret;
		if (m_rec == NULL)
			return -RECORD_ERR_INVAL;
		if (m_rec->state < RECORD_STATE_READY)
			return -RECORD_ERR_NOT_READY;
		if (m_rec->state == RECORD_STATE_RECORDING)
			return 0;

		ret = start_record_internal((HWAVEIN)m_rec->wavein_hdl, (WAVEHDR*)m_rec->bufheader, m_rec->bufcount);
		if (ret == 0)
			m_rec->state = RECORD_STATE_RECORDING;

		errno_t err = fopen_s(&fdwav, VERIFY_FILE_NAME, "wb+");
		if (err != 0) {
			printf("error open file failed\n");
			return -1;
		}
		//预留头部位置
		fseek(fdwav, sizeof(WavPCMFileHeader), SEEK_SET);

		return ret;
	}

	int winrec::stopRecord()
	{
		int ret;
		if (m_rec == NULL)
			return -RECORD_ERR_INVAL;
		if (m_rec->state < RECORD_STATE_RECORDING)
			return 0;

		m_rec->state = RECORD_STATE_STOPPING;

		MMRESULT res;
		res = waveInReset((HWAVEIN)m_rec->wavein_hdl);
		if (MMSYSERR_NOERROR != res)
			ret = 0 - res;
		else
			ret = 0;

		if (ret == 0) {
			m_rec->state = RECORD_STATE_READY;
		}

		close_recorder();

		//写入文件头
		fseek(fdwav, 0, SEEK_SET);
		WavPCMFileHeader h(winrec::m_iTotalDataLength);
		fwrite(&h, 1, sizeof(h), fdwav);

		if (fdwav) {
			fclose(fdwav);
			fdwav = NULL;
		}
		return ret;
	}

	bool winrec::isRecordStopped()
	{
		if (m_rec->state == RECORD_STATE_RECORDING)
			return false;

		unsigned int i;
		WAVEHDR* header;

		header = (WAVEHDR*)(m_rec->bufheader);
		/* after close, already free */
		if (header == NULL || m_rec->bufcount == 0)
			return true;

		for (i = 0; i < m_rec->bufcount; ++i) {
			if ((header)->dwFlags & WHDR_INQUEUE)
				return false;
			/* after stop, we called the waveInReset to return all buffers */
			/* dwUser, see data_proc; */
			if (header->dwUser != 0)
				return false;

			header++;
		}
		return true;
	}

	static void write_to_file(char* data, size_t length)
	{
		size_t wrt = 0, already = 0;
		int ret = 0;
		if (fdwav == NULL || data == NULL)
			return;

		while (1) {
			wrt = fwrite(data + already, 1, length - already, fdwav);
			if (wrt == (length - already))
				break;
			if (ferror(fdwav)) {
				ret = -1;
				break;
			}
			already += wrt;
		}
	}
}
posted @ 2023-08-08 15:43  YiXiaoKezz  阅读(29)  评论(0编辑  收藏  举报