2015年4月26日 星期日

CC2540/CC2541 : Exploit Non-Volatile Memory for Changing Scan Response Data (SRP) Dynamically


     How to change the SRP data is a very imperative for most bluetooth low energy use case. But in official Texas Instruments(TI) example code, there is no demonstration  for this purpose. In here, I note how to reach the goal.

    In the TI forum , it sai possible to update RSP data by disabling/enabling advertising.  But for my feeble trying, the RSP do not be updated. So I use the "reboot" as my method.

   Before achieving the topic goal, I would like to refer to how to add uart receiving function in example SimpleBLEPeripheral. The is based on ghostyu's example (in simplified chinese).


0. Copy while SimpleBLEPeripheral folder as a new folder SimpleBLEPeripheral_SerialPrint.

1. Add those file in your IAR project:

serialcommunication.c :



#include "bcomdef.h"
#include "OSAL.h"
#include "OSAL_PwrMgr.h"

#include "OnBoard.h"
#include "hal_uart.h"
#include "hal_lcd.h"
#include "serialcommunication.h"

#if defined ( PLUS_BROADCASTER )
  #include "peripheralBroadcaster.h"
#else
  #include "peripheral.h"
#endif

void SerialInitTransport(void)
{
  halUARTCfg_t uartConfig;

  // configure UART
  uartConfig.configured           = TRUE;
  uartConfig.baudRate             = SBP_UART_BR;
  uartConfig.flowControl          = SBP_UART_FC;
  uartConfig.flowControlThreshold = SBP_UART_FC_THRESHOLD;//enable when flowControl is valid
  uartConfig.rx.maxBufSize        = SBP_UART_RX_BUF_SIZE;//UART RX 
  uartConfig.tx.maxBufSize        = SBP_UART_TX_BUF_SIZE;//UART TX
  uartConfig.idleTimeout          = SBP_UART_IDLE_TIMEOUT;
  uartConfig.intEnable            = SBP_UART_INT_ENABLE;//interrupt enable
  uartConfig.callBackFunc         = sbpSerialAppCallback;//uart callback function

  // start UART
  // Note: Assumes no issue opening UART port.
  (void)HalUARTOpen( SBP_UART_PORT, &uartConfig );

  return;
}/*serialAppInitTransport*/


uint16 numBytes;

void sbpSerialAppCallback(uint8 port, uint8 event)
{
  uint8  pktBuffer[SBP_UART_RX_BUF_SIZE];
  // unused input parameter; PC-Lint error 715.
  (void)event;
  HalLcdWriteString("Data form my UART:", HAL_LCD_LINE_4 );
  //return lengh
  if( (numBytes = Hal_UART_RxBufLen(port)) > 0)
  {      
      //int j;
        //read all data
 (void)HalUARTRead(port, pktBuffer, numBytes);
        //uint8 blankStr[HAL_LCD_MAX_CHARS];
       // memset(&blankStr[0], 32, HAL_LCD_MAX_CHARS);
                      
 HalLcdWriteString(pktBuffer, HAL_LCD_LINE_5 );
        
        //GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, 
        //  numBytes, &pktBuffer[0] );
        
        //simpleProfileChar1 = pktBuffer[0];
  }/*if */
  
}/*sbpSerialAppCallback*/


static uint8 sendMsgTo_TaskID;

void SerialApp_Init( uint8 taskID )
{
  SerialInitTransport(); 
  sendMsgTo_TaskID = taskID; //save task id, for spare using.
}/*SerialApp_Init*/


serialcommunication.h


#ifndef _SERIAL_COMMUNICATION_H_
#define _SERIAL_COMMUNICATION_H_

#ifdef __cplusplus
extern "C"
{
#endif
  

#define SBP_UART_PORT                  HAL_UART_PORT_0
//#define SBP_UART_FC                    TRUE
#define SBP_UART_FC                    FALSE
#define SBP_UART_FC_THRESHOLD          48
#define SBP_UART_RX_BUF_SIZE           128
#define SBP_UART_TX_BUF_SIZE           128
#define SBP_UART_IDLE_TIMEOUT          6
#define SBP_UART_INT_ENABLE            TRUE
#define SBP_UART_BR                     HAL_UART_BR_57600 


// Serial Port Related
extern void SerialApp_Init(uint8 taskID);
extern void sbpSerialAppCallback(uint8 port, uint8 event);
void SerialInitTransport();

#ifdef __cplusplus
}
#endif

#endif/*_SERIAL_COMMUNICATION_H_*/


3. Modify simpleBLEPeripheral.c :

Add include  (about line 82):


#if defined FEATURE_OAD
  #include "oad.h"
  #include "oad_target.h"
#endif

#include "serialcommunication.h"
/*********************************************************************
 * MACROS
 */



Initialize uart (in function void SimpleBLEPeripheral_Init( uint8 task_id ), about line 286):


void SimpleBLEPeripheral_Init( uint8 task_id )
{
  simpleBLEPeripheral_TaskID = task_id;

  SerialApp_Init(task_id);
  // Setup the GAP



Now, you could use a serial com-port program(like sscom) to send data via uart.


    I assume the new SRP data is from UART, and received data be only the new SRP string. Modifying the UART callback function, to add some processing for save RSP values:


uint16 numBytes;

void sbpSerialAppCallback(uint8 port, uint8 event)
{
  uint8 pktBuffer[SBP_UART_RX_BUF_SIZE];
  // unused input parameter; PC-Lint error 715.
  (void)event;
  HalLcdWriteString("Data form my UART:", HAL_LCD_LINE_4 );
  
  //return length
  if( 0 < (numBytes = Hal_UART_RxBufLen(port)))
  {      
    
 /*that should use timer + process_event to update LCD content*/   
    //(void)HalUARTRead(port, pktBuffer, numBytes);        
 
 /* 
  baud rate = 57600-> byterate = 7200, it is, for one byte,  it needs 138 us 
  nop cast 1 clock, for CC254X, the crytal be 32MHz
  it is needed to be dalay over 138us ->  4416 nop operations
  
  if there is no delay in here, the uart-in data would be lost.
  
  most persons use 6000 (192us) as delay constant (a superstition?)
 */
    int i = 0;
    for(i = 0 ;0 < 6000;i++) 
  asm("nop");
  
  
 /*string sequence may be ended at 0x0A(enter) 0x0D(new line)*/
    if(0x0A == pData[numBytes - 1] || 0x0D == pData[numBytes - 1])
  numBytes--;
        
 if(0x0A == pData[numBytes - 2] || 0x0D == pData[numBytes - 2])
  numBytes--; 
 
        uint8 buffer[32];
        uint16 scanRspDataSize;
        
        memset(&buffer[0], 0, 32);                

        osal_snv_write(NV_MEM_ID, 32, &buffer[0]);
        
        SetRspData(&pData[0], numBytes, &buffer[0], &scanRspDataSize);              
        osal_snv_write(NV_MEM_ID, scanRspDataSize, &buffer[0]);                                  
        HAL_SYSTEM_RESET();          
  }/*if */
  
}/*sbpSerialAppCallback*/


  that is,clean the non-volatile memory, and save the organized RSP data, then reboot.

the NV_MEM_ID is 0xFE, it is my choice only. you could use other value.

the SetRspData function, which is for organizing data as RSP format, be :
(I wrote it in simpleBLEPeripheral.c)


void SetRspData(uint8 *pName, uint16 nameLen, 
                uint8 *pRspData, uint16 *pScanRspDataSize)
{       

  if(nameLen  > 31 - (2 + (5 + 1) + (1 + 2)) )
    nameLen =  31 - (2 + (5 + 1) + (1 + 2));
    
  pRspData[0] = nameLen + 1;
  pRspData[1] = GAP_ADTYPE_LOCAL_NAME_COMPLETE;
  memcpy(&pRspData[2], pName, nameLen); 
 
  int16  i;
  
  i = nameLen + 2; 
  pRspData[i++] = 0x05;
  pRspData[i++] = GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE;
  pRspData[i++] = LO_UINT16( DEFAULT_DESIRED_MIN_CONN_INTERVAL );     
  pRspData[i++] = HI_UINT16( DEFAULT_DESIRED_MIN_CONN_INTERVAL );
  pRspData[i++] = LO_UINT16( DEFAULT_DESIRED_MAX_CONN_INTERVAL );     
  pRspData[i++] = HI_UINT16( DEFAULT_DESIRED_MAX_CONN_INTERVAL );
  
  pRspData[i++] = 0x02;
  pRspData[i++] =GAP_ADTYPE_POWER_LEVEL;
  pRspData[i++] = 0;       // 0dBm;    
  
  *pScanRspDataSize =  i;
}/*SetRspData*/


Now modify the void SimpleBLEPeripheral_Init( uint8 task_id ) in simpleBLEPeripheral.c, about line 315 :


 // Set the GAP Role Parameters    
    
    GAPRole_SetParameter( GAPROLE_ADVERT_ENABLED, sizeof( uint8 ), &initial_advertising_enable );
    GAPRole_SetParameter( GAPROLE_ADVERT_OFF_TIME, sizeof( uint16 ), &gapRole_AdvertOffTime );
       
    uint8 defaultName[32];
    
    memset(&defaultName[0], 0, 32);
    sprintf((char*)&defaultName[0], "defaultRSP");
    
    uint8 buffer[32];    
    memset(&buffer[0], 0, 32);
      
    uint8 ret;       
   
    uint16 scanRspDataSize;
    scanRspDataSize = 0;  

    ret = osal_snv_read(NV_MEM_ID, 32 , &buffer[0]);
   
    if(SUCCESS == ret )  
    {      
      int i;      
      scanRspDataSize = 0;       
      scanRspDataSize = 1 + buffer[0];      
      i = 1 + buffer[0];
      scanRspDataSize += 1 + buffer[i];
      i += 1 + buffer[i];      
      scanRspDataSize += 1 + buffer[i];
    }/*if*/    
    
    if(NV_OPER_FAILED == ret)
    {
      memset(&buffer[0], 0, 31);      
      SetRspData(&defaultName[0], strlen((char*)&defaultName[0]), 
                 &buffer[0], &scanRspDataSize);      
      //ret = osal_snv_read(0xfe, 32 , &buffer[0]);
      //osal_snv_write(0xfe, sizeof(scanRspData), &scanRspData[0]);
    }/*if */
       
    
    GAPRole_SetParameter( GAPROLE_SCAN_RSP_DATA, scanRspDataSize, &buffer[0] );



That is, to read the non-volatile memory: if there is no data, use default name, otherwise use saved data. That data would be used for SRP, the peripheral name.


If you use serial comport program to send a string into the CC2540 (or CC2541), the board would reboot, and the string be the new peripheral name.

Extra:

If you would like to print data to uart (that could use for echoing), the serialcommunication.c could be add function SerialPrintf :


// ref : http://bbs.elecfans.com/jishu_431223_2_1.html#comment_top

#ifdef HAL_UART_DMA_TX_MAX
  #define PRINT_BUF_LEN HAL                 UART_DMA_TX_MAX
#else
  #define PRINT_BUF_LEN                     (128)
#endif

int SerialPrintf(const char *fmt, ...)
{
    uint32  ulLen;
    va_list ap;

    char *pBuf;
    
    pBuf = (char*)osal_mem_alloc(PRINT_BUF_LEN);  
    
    va_start(ap, fmt);
    ulLen = vsprintf(pBuf, fmt, ap);       
    va_end(ap);

    HalUARTWrite(HAL_UART_PORT_0, (uint8*)pBuf, ulLen); 
    osal_mem_free(pBuf);    
    
    return ulLen;    
}/*SerialPrintf*/

And do not forget adding include head to avoid compiling warning.


#include "stdarg.h"
#include "stdio.h"


2015年4月9日 星期四

Build Qt 4.8.6 or 4.8.7 in windows by VS2005

NOTE : Qt 4.8.7 would be planned to be the last version of Qt 4. If your software be required porting to mobile devices deeply, or would be maintained for very long time in the future desktop platform, Qt 5 may be a better selection.

       In this article, I will explain how to build Qt 4.8 in windows by VS 2005. I take Qt 4.8.6 or 4.8.7 as the building target.


Step 0, Download the version 4.8.6 or 4.8.7, and uncompress the package. For me, the source code of Qt is in C:\Lib\Qt\qt-everywhere-opensource-src-4.8.6 (or ...src-4.8.7)


Step 1(skipable): modify the file, qt-everywhere-opensource-src-4.8.6\mkspecs\win32-msvc2005, line 23:


QMAKE_CFLAGS_RELEASE    = -O2 -MD
QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MD -Zi
QMAKE_CFLAGS_DEBUG      = -Zi -MDd




to

QMAKE_CFLAGS_RELEASE    = -O2 -MT
QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MT -Zi
QMAKE_CFLAGS_DEBUG      = -Zi -MTd



Step 2: Modify the file, qt-everywhere-opensource-src-4.8.6\src\gui\kernel\qclipboard_win.cpp, line 304 as:


static bool isProcessBeingDebugged(HWND hwnd)
{
#if defined(_MSC_VER) && _MSC_VER <= 1400 
    return false;
#else
    DWORD pid = 0;
    if (!GetWindowThreadProcessId(hwnd, &pid) || !pid)
        return false;
    const HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
    if (!processHandle)
       return false;
    BOOL debugged = FALSE;
    CheckRemoteDebuggerPresent(processHandle, &debugged);
    CloseHandle(processHandle);
    return debugged != FALSE;
#endif    
}



Step 3: Modify the file, qt-everywhere-opensource-src-4.8.6\src\3rdparty\webkit\Source\WebCore\platform\DefaultLocalizationStrategy.cpp,  line 237(4.8.6)
or  line 317 (4.8.7) As:

String DefaultLocalizationStrategy::contextMenuItemTagLookUpInDictionary(const String& selectedString)
{
#if defined(BUILDING_ON_LEOPARD) || defined(BUILDING_ON_SNOW_LEOPARD)
    UNUSED_PARAM(selectedString);
    return WEB_UI_STRING("Look Up in Dictionary", "Look Up in Dictionary context menu item");
#else
#if USE(CF)
    RetainPtr<CFStringRef> selectedCFString(AdoptCF, truncatedStringForLookupMenuItem(selectedString).createCFString());
    return formatLocalizedString(WEB_UI_STRING("Look Up “%@”", "Look Up context menu item with selected word"), selectedCFString.get());
#else
#if(0)
    return WEB_UI_STRING("Look Up “<selection>”", "Look Up context menu item with selected word").replace("<selection>", truncatedStringForLookupMenuItem(selectedString));
#else    
 return WEB_UI_STRING("Look Up <selection>", "Look Up context menu item with selected word").replace("<selection>", truncatedStringForLookupMenuItem(selectedString));
#endif
#endif
#endif
}

4.
   - α. Create a folder, which would contain the built files, for me, it be C:\Lib\Qt\qt-everywhere-opensource-src-4.8.6\built. Copy mkspecs (folder of Step 1.) folder into this folder.

  - β. (※ for 4.8.7) , add this icon :
into the folder : C:\Lib\Qt\qt-everywhere-opensource-src-4.8.7\src\3rdparty\webkit\Source\WebCore\inspector\front-end\Image. DO NOT change the file name or format, or it would cause compiling fail.


5. open a VS2005 command line prompt to configuration Qt.



The configure arguments, for me , I use

configure.exe -prefix C:\Lib\Qt\qt-everywhere-opensource-src-4.8.6\built -platform win32-msvc2005 -shared -debug-and-release -nomake demos -nomake examples -fast -mp -opensource -stl 

where the path after -prefix should be the same one which be mentioned at Step 4.( the one would contain built files.) .
(for me,
C:\Lib\Qt\qt-everywhere-opensource-src-4.8.6\built
for 4.8.6

C:\Lib\Qt\qt-everywhere-opensource-src-4.8.7\built
,for 4.8.7.)

Press enter key then y, then the nmake file would be generated.


6. After the gernerating has been done, type "nmake" and press enter, wait the binaries has been generated.The building of code cast long time, which depends on how much money you put in your computer.



7. type nmake install, to organize the built folder. DO NOT copy directly "include" folder which lies in the qt source root folder to your destination. The original include folder depends on source code heavily, it is not for working with built libraries.
  After nmake install finish, the librarie, include files and binaries tools are very portable, you could carry them to another windows computer for development usage, and the package would works well.

2015年4月8日 星期三

Dump H264 Raw Data From LIVE555 Client


      Maybe lots persons encounter the same circumstances : after the succession of building LIVE555, and execute the testRTSPClient to connect a rtsp server: the debug log  seems be pettey good; But when we damp the h264 data to be played by VLC or ffplay, that is unplayable. Of cource, if we deliver the h264 data to a h264 decoder directly, there would occur error always.


   I found there is no article being concise with complete code  in the website, I fill it in here.

The code base on LIVE555\testProgs\testRTSPClient.cpp.

modify the constructor and destructor of DummySink as below, it is about line 479:


unsigned char *pH264 = NULL;

DummySink::DummySink(UsageEnvironment& env, MediaSubsession& subsession, char const* streamId)
  : MediaSink(env),
    fSubsession(subsession) {
  fStreamId = strDup(streamId);
  fReceiveBuffer = new u_int8_t[DUMMY_SINK_RECEIVE_BUFFER_SIZE];
  pH264 = (unsigned char*)malloc(256*1024);
}

DummySink::~DummySink() {
  delete[] fReceiveBuffer;
  delete[] fStreamId;
  if(NULL != pH264)
   free(pH264);
}



And modify afterGettingFrame (the one with debug log ) as


void DummySink::afterGettingFrame(unsigned frameSize, unsigned numTruncatedBytes,
      struct timeval presentationTime, unsigned /*durationInMicroseconds*/) {
#if(0)
  // We've just received a frame of data.  (Optionally) print out information about it:
#ifdef DEBUG_PRINT_EACH_RECEIVED_FRAME
  if (fStreamId != NULL) envir() << "Stream \"" << fStreamId << "\"; ";
  envir() << fSubsession.mediumName() << "/" << fSubsession.codecName() << ":\tReceived " << frameSize << " bytes";
  if (numTruncatedBytes > 0) envir() << " (with " << numTruncatedBytes << " bytes truncated)";
  char uSecsStr[6+1]; // used to output the 'microseconds' part of the presentation time
  sprintf(uSecsStr, "%06u", (unsigned)presentationTime.tv_usec);
  envir() << ".\tPresentation time: " << (int)presentationTime.tv_sec << "." << uSecsStr;
  if (fSubsession.rtpSource() != NULL && !fSubsession.rtpSource()->hasBeenSynchronizedUsingRTCP()) {
    envir() << "!"; // mark the debugging output to indicate that this presentation time is not RTCP-synchronized
  }
#ifdef DEBUG_PRINT_NPT
  envir() << "\tNPT: " << fSubsession.getNormalPlayTime(presentationTime);
#endif
  envir() << "\n";
#endif
#else
  printf("__FUNCTION__  = %s\n", __FUNCTION__ );
 if(0 == strncmp(fSubsession.codecName(), "H264", 16))
 { 
  unsigned char nalu_header[4] = { 0, 0, 0, 1 };   
  unsigned char extraData[256]; 
  unsigned int num = 0;    
  
  SPropRecord *pSPropRecord;
  pSPropRecord = parseSPropParameterSets(fSubsession.fmtp_spropparametersets(), num);  
  
  unsigned int extraLen;
  extraLen = 0;

  //p_record[0] is sps 
  //p+record[1] is pps
  for(unsigned int i = 0; i < num; i++){ 
   memcpy(&extraData[extraLen], &nalu_header[0], 4);
   extraLen += 4;
   memcpy(&extraData[extraLen], pSPropRecord[i].sPropBytes, pSPropRecord[i].sPropLength);
   extraLen += pSPropRecord[i].sPropLength;
  }/*for i*/

  memcpy(&extraData[extraLen], &nalu_header[0], 4);
  extraLen += 4;

  delete[] pSPropRecord ;  

  memcpy(pH264, &extraData[0], extraLen);
  memcpy(pH264 + extraLen, fReceiveBuffer, frameSize); 
  
  int totalSize;
  totalSize = extraLen + frameSize;

  static FILE *fp = fopen("saved.h264", "wb");

  fwrite(pH264, 1,  totalSize, fp);
  fflush(fp);
  printf("\tsaved %d bytes\n", totalSize);
 }/*if 0 == strncmp(fSubsession.codecName(), "H264", 16)*/
#endif  
  // Then continue, to request the next frame of data:
  continuePlaying();
}





That is, LIVE555 server  omits the SPS(Sequence Parameter Set) and PPS(Picture Parameter Set) parser information of each h264 slice.

Here  I compliment SPS and PPS  flag, which be "0x00 0x00 0x00 0x01".


You could refer to here for more detail about SPS and PPS.

Playback Audio Data From Memory in Windows


Continue previous article : Understand wave format, and implement a wave reader , In here , I will demonstrate how to play audio in windows.

(Zeroth are wave format, you could refer to previous article.)

First , the struct WAVEFORMATEX would be used, the members of WAVEFORMATEX are very explicit, I do not explain that.


Second, the structure WAVEHDR, which is for managing audio buffer. One should set audio buffer in the WAVEHDR structure  to require windows to play that audio.


The functions will be used:

waveOutOpen : open the audio output device for playback.
         The function  needs a HWAVEOUT as output.
         One should have set WAVEHDR to feed the waveOutOpen.
         The function needs to set a callback function, waveOutProc.
          
waveOutPrepareHeader/waveOutPrepareHeader:
        Set the the structure WAVEHDR be prepared/cleaned.

 waveOutWrite : Set the audio data to the audio output device.


To ensure the audio playback be smooth, it is necessary to use 2 block of buffer:
one for preparing, one for playing, in interleave form(very similar to producer consumer semaphores problem). In here, I use EnterCriticalSection/LeaveCriticalSection.



My code is below, the full code with previouse is too long, I omit the previous code:
The part should be inserted after the line:



The code assume the length of wave data is  multiple of SEND_SIZE(8KB).


#define MAX_STR_LEN       256


#define KB         (1024)
#define SEND_SIZE       (8*KB)

#define BLOCK_SIZE       (16*KB)
#define BLOCK_COUNT       (2)


#include <windows.h>
#include <Mmreg.h>

CRITICAL_SECTION waveCriticalSection;
WAVEFORMATEX wFmt; /*for debug, or the should not be global variable*/

void CALLBACK WaveOutProc( HWAVEOUT device,         
                  UINT uMsg,            
                   DWORD dwInstance,     
                   DWORD dwParam1,       
                   DWORD dwParam2 )   
{   


 static unsigned int cnt = 0;
 
 DWORD *pFreeBlockCounter;
 pFreeBlockCounter = (DWORD*)(long long)dwInstance; 


 switch(uMsg)
 {      
 case WOM_DONE:  
  
  printf("playing time = %1.3f s\n", cnt*BLOCK_SIZE/(float)(wFmt.nAvgBytesPerSec));  
  cnt++;   

  EnterCriticalSection(&waveCriticalSection);
  (*pFreeBlockCounter)++;
  LeaveCriticalSection(&waveCriticalSection); 
  break;

 case WOM_OPEN:
  InitializeCriticalSection(&waveCriticalSection);
  break;
 case WOM_CLOSE:
  DeleteCriticalSection(&waveCriticalSection);
  break;
 }/*if uMsg == WOM_DONE*/   

}/*WaveCallBack*/ 

Below code should be insert in front of the "return" line in main:

 char *pAudioData;
 
 pAudioData = (char*)malloc(audioDataLen);

 fread(pAudioData, 1, audioDataLen, pWaveFile);

 fclose(pWaveFile);


 /*step 0, variables*/ 
 MMRESULT ret;

 HWAVEOUT hDevice;
 int waveFreeBlockCount;
 WAVEHDR *pWaveHeaderWithBlock; 
 int waveCurrentBlock;
 
 /*step 1: initialize */

 hDevice = NULL;
 /*set WAVEFORMATEX*/
 wFmt.wFormatTag = (WORD)WAVE_FORMAT_PCM;
 wFmt.nSamplesPerSec = (WORD)nSamplesPerSec;
 wFmt.wBitsPerSample = (WORD)bitsPerSample;
 wFmt.nChannels = (WORD)nChannel;
 
 wFmt.nBlockAlign = (WORD)(wFmt.nChannels*(bitsPerSample/8));
 wFmt.nAvgBytesPerSec = wFmt.nSamplesPerSec* wFmt.nBlockAlign; 
 wFmt.cbSize = 0;
 
 /*set up callback function to play audio*/
 ret = waveOutOpen( &hDevice, WAVE_MAPPER, &wFmt, (DWORD_PTR)WaveOutProc, 
  (DWORD_PTR)&waveFreeBlockCount, CALLBACK_FUNCTION);

 if(0 != ret)
 {
  char errStr[MAX_STR_LEN];

  waveOutGetErrorText(ret, &errStr[0], MAX_STR_LEN);
  printf("error = %s\n", &errStr[0]);
  goto Flag_end_main;
 }/*if */

   
 /*
 * prepare and  set up buffer for struct WAVEHDR 
  use 2 buffer to load audio data in interleave form
 */
 pWaveHeaderWithBlock = (WAVEHDR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
  (BLOCK_SIZE + sizeof(WAVEHDR)) * BLOCK_COUNT);   
 
 if(NULL == pWaveHeaderWithBlock)
  goto Flag_end_main;

 
 WAVEHDR *pBlockHeader;

 pBlockHeader = (WAVEHDR*)pWaveHeaderWithBlock;
 
 for(int i = 0; i < BLOCK_COUNT; i++) {
  pBlockHeader[i].dwBufferLength = BLOCK_SIZE;
  pBlockHeader[i].lpData  = 
   (LPSTR)(pWaveHeaderWithBlock) + sizeof(WAVEHDR) * BLOCK_COUNT + i*BLOCK_SIZE;  
 }/*for i*/ 

 waveFreeBlockCount = BLOCK_COUNT;
 waveCurrentBlock = 0; 
 
 if(NULL == pWaveHeaderWithBlock) 
  goto Flag_end_main;
 

 unsigned int remainderLen;

 remainderLen = audioDataLen;
 char *pMovAudioData;

 pMovAudioData = pAudioData;
 
 
 pMovAudioData += (unsigned int)(nSamplesPerSec*nChannel*2);
 remainderLen -= (unsigned int)(nSamplesPerSec*nChannel*2);

 unsigned int cnt;
 cnt = 0;
 while(remainderLen > SEND_SIZE)
 {  
  printf("send time = %1.3f s\n", cnt*SEND_SIZE/((float)bytesPerSec) );
  cnt++;

  /*
  * Step 3 play audio ! 
  */
  WAVEHDR* pCurrentWaveHeader;
  unsigned long remain;    
  unsigned int size;
  char *pData;

  size = SEND_SIZE;
  pCurrentWaveHeader = &pWaveHeaderWithBlock[waveCurrentBlock]; 
  pData = (char*)pMovAudioData;

  while(size > 0) 
  {
   /*
    make sure the header we're going to use is unprepared
     cleans up the preparation     
   */
  
   if(WHDR_PREPARED & pCurrentWaveHeader->dwFlags)
    waveOutUnprepareHeader(hDevice, pCurrentWaveHeader, sizeof(WAVEHDR));
   

   if(size < (int)(BLOCK_SIZE - pCurrentWaveHeader->dwUser)) 
   { /*
     the buffer is  enough, stow all data in current WAVEHDR's buffer.
    */
    memcpy(pCurrentWaveHeader->lpData + pCurrentWaveHeader->dwUser, pData, size);
    pCurrentWaveHeader->dwUser += size;   
    break;
   }/*if*/

   /*the buffer would be full */
   remain = BLOCK_SIZE - (unsigned long)pCurrentWaveHeader->dwUser;
   memcpy(pCurrentWaveHeader->lpData + pCurrentWaveHeader->dwUser, pData, remain);

   /*remaining would be stowed in the other WAVEHDR, note the size here */
   size -= remain;
   pData += remain;
   
   /*full size of the WAVEHDR's buffer is BLOCK_SIZE */
   pCurrentWaveHeader->dwBufferLength = BLOCK_SIZE;

   /*preparation...*/ 
   waveOutPrepareHeader(hDevice, pCurrentWaveHeader, sizeof(WAVEHDR));
   /*write the data to audio device */
   waveOutWrite(hDevice, pCurrentWaveHeader, sizeof(WAVEHDR));

   /*
    the data or previous data is under playing
    assure the counter waveFreeBlockCount would not be disorder 
   */
   EnterCriticalSection(&waveCriticalSection);
   waveFreeBlockCount--;
   LeaveCriticalSection(&waveCriticalSection);

   /*
   * wait for there is block in free
   */
   while(0 == waveFreeBlockCount)   
    Sleep(5);
   
   /*
   * point to the next block
   */

   waveCurrentBlock++;
   waveCurrentBlock %= BLOCK_COUNT;
   pCurrentWaveHeader = &pWaveHeaderWithBlock[waveCurrentBlock];
   pCurrentWaveHeader->dwUser = 0;
  }/*while*/


  pMovAudioData += SEND_SIZE;
  remainderLen -= SEND_SIZE;  
 }/*while remainderLen > SEND_SIZE*/

 //SendAudioBuf(pMovAudioData, remainderLen);


Flag_end_main:

 for(int i = 0; i < waveFreeBlockCount; i++){
  if(NULL != pWaveHeaderWithBlock)
  {
   if(pWaveHeaderWithBlock[i].dwFlags & WHDR_PREPARED)
    waveOutUnprepareHeader(hDevice, &pWaveHeaderWithBlock[i], sizeof(WAVEHDR));
  }/*if*/
  pWaveHeaderWithBlock = NULL;
 }/*for i waveFreeBlockCount*/

 if(NULL != hDevice) 
  waveOutClose(hDevice);
 hDevice = NULL;
 
 
 if(NULL != pWaveHeaderWithBlock)
  HeapFree(GetProcessHeap(), 0, pWaveHeaderWithBlock);

 pWaveHeaderWithBlock = NULL;

 
Now, you have a hand-made wave player in windows.