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