Introduction
The article ‘Use mplayer as our audio decoder’ introduces how to set compile environment and how to build mplayer as a single DLL library, and use it as a decoder to decode more audio format and playback.
This article will go to the next step – add real time spectrum analyzer feature to display spectrum during the play process.
Modify libao2
In Windows, we could use DSound
or WaveOut
for audio playback, the DSound
play is the first choice in mplayer.
First, we define a few control flags for spectrum analyzer.
#define AOCONTROL_START 913
#define AOCONTROL_GET_POSITION 914
#define AOCONTROL_FLUSH 915
- The flag
AOCONTROL_START
for start spectrum analyzer. - The flag
AOCONTROL_GET_POSITION
for get play position in DSound
’s play buffer. - The flag
AOCONTROL_FLUSH
for seek is called in playback process.
Second, we should modify libao2/ao_dsound.c and libao2/ao_win32.c.
libao2/ao_dsound.c
static void DestroyBuffer(void)
{
if (hdsbuf) {
IDirectSoundBuffer_Release(hdsbuf);
hdsbuf = NULL;
}
if (hdspribuf) {
IDirectSoundBuffer_Release(hdspribuf);
hdspribuf = NULL;
}
}
static int write_buffer(unsigned char *data, int len)
{
HRESULT res;
LPVOID lpvPtr1;
DWORD dwBytes1;
LPVOID lpvPtr2;
DWORD dwBytes2;
res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len,
&lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
if (DSERR_BUFFERLOST == res)
{
IDirectSoundBuffer_Restore(hdsbuf);
res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len,
&lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0);
}
if (SUCCEEDED(res))
{
if( (ao_data.channels == 6) && !AF_FORMAT_IS_AC3(ao_data.format) ) {
const int chantable[6] = {0, 1, 4, 5, 2, 3}; int i, j;
int numsamp,sampsize;
sampsize = af_fmt2bits(ao_data.format)>>3; numsamp = dwBytes1 / (ao_data.channels * sampsize);
for( i = 0; i < numsamp; i++ ) for( j = 0; j < ao_data.channels; j++ ) {
memcpy(lpvPtr1+(i*ao_data.channels*sampsize)+
(chantable[j]*sampsize),data+(i*ao_data.channels*sampsize)+
(j*sampsize),sampsize);
}
if (NULL != lpvPtr2 )
{
numsamp = dwBytes2 / (ao_data.channels * sampsize);
for( i = 0; i < numsamp; i++ ) for( j = 0; j < ao_data.channels; j++ ) {
memcpy(lpvPtr2+(i*ao_data.channels*sampsize)+
(chantable[j]*sampsize),data+dwBytes1+
(i*ao_data.channels*sampsize)+(j*sampsize),sampsize);
}
}
write_offset+=dwBytes1+dwBytes2;
if (write_offset>=buffer_size)write_offset-=buffer_size; } else {
fast_memcpy(lpvPtr1,data,dwBytes1);
if (NULL != lpvPtr2 )fast_memcpy(lpvPtr2,data+dwBytes1,dwBytes2);
write_offset+=dwBytes1+dwBytes2;
if (write_offset>=buffer_size)write_offset-=buffer_size; }
res = IDirectSoundBuffer_Unlock(hdsbuf,lpvPtr1,dwBytes1,lpvPtr2,dwBytes2);
if (SUCCEEDED(res))
{
DWORD status;
IDirectSoundBuffer_GetStatus(hdsbuf, &status);
if (!(status & DSBSTATUS_PLAYING)) {
res = IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING);
}
return dwBytes1+dwBytes2;
}
}
return 0;
}
static int control(int cmd, void *arg)
{
DWORD volume;
switch (cmd) {
case AOCONTROL_GET_VOLUME: {
ao_control_vol_t* vol = (ao_control_vol_t*)arg;
IDirectSoundBuffer_GetVolume(hdsbuf, &volume);
vol->left = vol->right = pow(10.0, (float)(volume+10000) / 5000.0);
return CONTROL_OK;
}
case AOCONTROL_SET_VOLUME: {
ao_control_vol_t* vol = (ao_control_vol_t*)arg;
volume = (DWORD)(log10(vol->right) * 5000.0) - 10000;
IDirectSoundBuffer_SetVolume(hdsbuf, volume);
return CONTROL_OK;
}
case AOCONTROL_START:
{
return CONTROL_OK;
}
case AOCONTROL_FLUSH:
{
return CONTROL_OK;
}
case AOCONTROL_GET_POSITION:
{
DWORD playCursor, writeCursor;
int count = 0;
long long* pos = NULL;
if(!arg) return -1;
pos = (long long*)arg;
if(FAILED(IDirectSoundBuffer_GetCurrentPosition
(hdsbuf, &playCursor, &writeCursor)))
{
return -1;
}
count = write_offset / ao_data.bps;
*pos = playCursor + count * ao_data.bps;
return CONTROL_OK;
}
}
return -1;
}
libao2/ao_win32.c
static int play_cursor = 0;
static int control(int cmd,void *arg)
{
DWORD volume;
switch (cmd)
{
case AOCONTROL_GET_VOLUME:
{
ao_control_vol_t* vol = (ao_control_vol_t*)arg;
waveOutGetVolume(hWaveOut,&volume);
vol->left = (float)(LOWORD(volume)/655.35);
vol->right = (float)(HIWORD(volume)/655.35);
mp_msg(MSGT_AO, MSGL_DBG2,"ao_win32: volume left:%f
volume right:%f\n",vol->left,vol->right);
return CONTROL_OK;
}
case AOCONTROL_SET_VOLUME:
{
ao_control_vol_t* vol = (ao_control_vol_t*)arg;
volume = MAKELONG(vol->left*655.35,vol->right*655.35);
waveOutSetVolume(hWaveOut,volume);
return CONTROL_OK;
}
case AOCONTROL_START:
{
play_cursor = 0;
return CONTROL_OK;
}
case AOCONTROL_FLUSH:
{
play_cursor = 0;
return CONTROL_OK;
}
case AOCONTROL_GET_POSITION:
{
long long* pos = NULL;
DWORD dwPosition = 0;
if(!arg) return -1;
pos = (long long*)arg;
dwPosition = play_cursor;
while(dwPosition > ao_data.bps)
dwPosition -= ao_data.bps;
*pos = dwPosition;
return CONTROL_OK;
}
}
return -1;
}
The Spectrum Analyzer Component
This component was developed for mplayer, which use libao2
’s functions that are included in mplayer’s source code, and we should make some modifications to it for spectrum analyzer component.
First, we should define a structure to hold an interface (pointer to ao_functions_t
, which is defined in audio_out.h) that is instanced by function ‘init_best_audio_out
’.
#ifndef INCLUDE_SPECTRUM_ANALYZER
#define INCLUDE_SPECTRUM_ANALYZER
#include <windows.h>
#include <mmsystem.h>
#include <dsound.h>
#include "audio_out.h"
typedef struct
{
int sample_rate;
int channels;
int bits_per_sample;
int frame_size;
long buffer_size;
unsigned long long write_bytes;
const ao_functions_t* funcs;
} ao_infoex_t;
#ifdef __cplusplus
extern "C" {
#endif
int spectrum_analyzer_init(HWND);
void* spectrum_analyzer_new(HWND, int, BOOL, ao_infoex_t*);
int spectrum_analyzer_writedata(void*, void*, long, long);
int spectrum_analyzer_render(void*, int);
int spectrum_analyzer_flush(void*);
int spectrum_analyzer_free(void*);
int spectrum_analyzer_uninit(void);
#ifdef __cplusplus
}
#endif
#endif
Use the Code – Display Spectrum Analyzer
- Create a window as spectrum analyzer window.
- Call function ‘
spectrum_analyzer_init
’ to initialize spectrum analyzer when creating window success. - Call function ‘
spectrum_analyzer_uninit
’ to uninitialized spectrum analyzer when window holds the WM_DESTROY
message. - Call function ‘
spectrum_analyzer_new
’ to instance an new spectrum analyzer. - Call function ‘
spectrum_analyzer_writedata
’ to write PCM data to feed spectrum analyzer after PCM data write to sound card. - Call function ‘
spectrum_analyzer_render
’ to render memory bitmap to spectrum analyzer window.
#include "wnd.h"
#include "spectrum_analyzer.h"
HWND hMainWindow;
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = _T("SpectrumAnalyzer");
wcex.hIconSm = LoadIcon(wcex.hInstance,
MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
DWORD dwStyle = WS_POPUPWINDOW & (~WS_SYSMENU) | WS_CAPTION;
hWnd = CreateWindow(_T("SpectrumAnalyzer"),
_T("Spectrum Analyzer for MPlayer - by jacky_zz"), dwStyle,
0, 0, 402, 120, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
spectrum_analyzer_init(hWnd);
hMainWindow = hWnd;
return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
spectrum_analyzer_uninit();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
#include "stdafx.h"
#include "DirectAudio.h"
#include "wnd.h"
#include "spectrum_analyzer.h"
typedef const void* (*init_best_audio_out)
(char** ao_list, int use_plugin, int rate, int channels, int format, int flags);
typedef int (*usec_sleep)(int usec_delay);
typedef int (*mp_decode_audio)(void* sh_audio , int minlen);
typedef void* (*mp_dll_open)(const char* fileName);
typedef void (*mp_dll_close)(void* _mpctx );
typedef void* (*mp_dll_get_sh_audio)(void* _mpctx );
typedef int (*mp_dll_get_audio_attributes)(int* samplerate,
int* channels, int* bits, int* format);
typedef void (*mp_dll_set_audio_attributes)(int samplerate, int channels, int bits);
typedef const void* (*mp_dll_get_audio_out)
(void* _mpctx);
typedef void (*mp_dll_set_audio_out)(void* _mpctx ,
const void* ao_out);
typedef int (*mp_dll_get_eof)(void* _mpctx );
typedef void (*mp_dll_set_eof)(void* _mpctx , int eof);
typedef int (*mp_dll_audio_out_get_space)(const void* audio_out );
typedef int (*mp_dll_audio_out_play)(const void* audio_out , char* buf, int size, int flag);
typedef int (*mp_dll_audio_eof)(void* _mpctx );
typedef char* (*mp_dll_sh_audio_get_out_buffer)(void* sh_audio );
typedef int (*mp_dll_sh_audio_get_out_buffer_len)(void* sh_audio );
typedef void (*mp_dll_sh_audio_set_out_buffer_len)(void* sh_audio , int len);
typedef int (*mp_dll_audio_out_get_info)(const void* audio_out , char* name, char* short_name, char* author, char* comment);
typedef int (*mp_dll_fill_buffer)(void* _mpctx , char* buf, int size);
DECLARE_FUNC_PTR_VAR(init_best_audio_out)
DECLARE_FUNC_PTR_VAR(usec_sleep)
DECLARE_FUNC_PTR_VAR(mp_decode_audio)
DECLARE_FUNC_PTR_VAR(mp_dll_open)
DECLARE_FUNC_PTR_VAR(mp_dll_close)
DECLARE_FUNC_PTR_VAR(mp_dll_get_sh_audio)
DECLARE_FUNC_PTR_VAR(mp_dll_get_audio_attributes)
DECLARE_FUNC_PTR_VAR(mp_dll_set_audio_attributes)
DECLARE_FUNC_PTR_VAR(mp_dll_get_audio_out)
DECLARE_FUNC_PTR_VAR(mp_dll_set_audio_out)
DECLARE_FUNC_PTR_VAR(mp_dll_get_eof)
DECLARE_FUNC_PTR_VAR(mp_dll_set_eof)
DECLARE_FUNC_PTR_VAR(mp_dll_audio_out_get_space)
DECLARE_FUNC_PTR_VAR(mp_dll_audio_out_play)
DECLARE_FUNC_PTR_VAR(mp_dll_audio_eof)
DECLARE_FUNC_PTR_VAR(mp_dll_sh_audio_get_out_buffer)
DECLARE_FUNC_PTR_VAR(mp_dll_sh_audio_get_out_buffer_len)
DECLARE_FUNC_PTR_VAR(mp_dll_sh_audio_set_out_buffer_len)
DECLARE_FUNC_PTR_VAR(mp_dll_audio_out_get_info)
DECLARE_FUNC_PTR_VAR(mp_dll_fill_buffer);
typedef struct
{
void* data;
void* data1;
void* data2;
} callback_t;
static void* handle = NULL;
void init(HMODULE hModule)
{
_init_best_audio_out = (init_best_audio_out)GetProcAddress
(hModule, "init_best_audio_out");
_usec_sleep = (usec_sleep)GetProcAddress(hModule, "usec_sleep");
_mp_decode_audio = (mp_decode_audio)GetProcAddress(hModule, "mp_decode_audio");
_mp_dll_open = (mp_dll_open)GetProcAddress(hModule, "mp_dll_open");
_mp_dll_close = (mp_dll_close)GetProcAddress(hModule, "mp_dll_close");
_mp_dll_get_sh_audio = (mp_dll_get_sh_audio)GetProcAddress
(hModule, "mp_dll_get_sh_audio");
_mp_dll_get_audio_attributes = (mp_dll_get_audio_attributes)
GetProcAddress(hModule, "mp_dll_get_audio_attributes");
_mp_dll_set_audio_attributes = (mp_dll_set_audio_attributes)
GetProcAddress(hModule, "mp_dll_set_audio_attributes");
_mp_dll_get_audio_out = (mp_dll_get_audio_out)
GetProcAddress(hModule, "mp_dll_get_audio_out");
_mp_dll_set_audio_out = (mp_dll_set_audio_out)
GetProcAddress(hModule, "mp_dll_set_audio_out");
_mp_dll_get_eof = (mp_dll_get_eof)GetProcAddress(hModule, "mp_dll_get_eof");
_mp_dll_set_eof = (mp_dll_set_eof)GetProcAddress(hModule, "mp_dll_set_eof");
_mp_dll_audio_out_get_space = (mp_dll_audio_out_get_space)
GetProcAddress(hModule, "mp_dll_audio_out_get_space");
_mp_dll_audio_out_play = (mp_dll_audio_out_play)
GetProcAddress(hModule, "mp_dll_audio_out_play");
_mp_dll_audio_eof = (mp_dll_audio_eof)
GetProcAddress(hModule, "mp_dll_audio_eof");
_mp_dll_sh_audio_get_out_buffer = (mp_dll_sh_audio_get_out_buffer)
GetProcAddress(hModule, "mp_dll_sh_audio_get_out_buffer");
_mp_dll_sh_audio_get_out_buffer_len = (mp_dll_sh_audio_get_out_buffer_len)
GetProcAddress(hModule, "mp_dll_sh_audio_get_out_buffer_len");
_mp_dll_sh_audio_set_out_buffer_len = (mp_dll_sh_audio_set_out_buffer_len)
GetProcAddress(hModule, "mp_dll_sh_audio_set_out_buffer_len");
_mp_dll_audio_out_get_info = (mp_dll_audio_out_get_info)
GetProcAddress(hModule, "mp_dll_audio_out_get_info");
_mp_dll_fill_buffer = (mp_dll_fill_buffer)
GetProcAddress(hModule, "mp_dll_fill_buffer");
}
DWORD CALLBACK GUIThread(void* lpParam)
{
callback_t* callback = reinterpret_cast<callback_t* />(lpParam);
HINSTANCE hInstance = reinterpret_cast<hinstance />(GetModuleHandle(NULL));
MyRegisterClass(hInstance);
if (!InitInstance(hInstance, SW_SHOW))
{
if (callback->data1)
SetEvent(callback->data1);
return 0;
}
MSG msg = {0};
HACCEL hAccelTable = {0};
ao_infoex_t* ao_infoex = reinterpret_cast<ao_infoex_t* />(callback->data);
handle = spectrum_analyzer_new(hMainWindow, 0, FALSE, ao_infoex);
if (callback->data2)
SetEvent(callback->data2);
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
spectrum_analyzer_free(handle);
handle = NULL;
if (callback->data1)
SetEvent(callback->data1);
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
TCHAR szFile[50 * MAX_PATH] = {0};
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = GetDesktopWindow();
ofn.lpstrFile = szFile;
ofn.lpstrFile[0] = _T('\0');
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = _T("Audio Files\0*.*\0");
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_EXPLORER;
if (!GetOpenFileName(&ofn))
return 0;
HMODULE hModule = LoadLibrary("mplayer.dll");
if (!hModule)
return 0;
init(hModule);
void* mpctx = _mp_dll_open(szFile);
if (!mpctx)
{
FreeLibrary(hModule);
return 0;
}
int samplerate, channels, bits, format;
void* sh_audio = NULL;
sh_audio = _mp_dll_get_sh_audio(mpctx);
_mp_dll_get_audio_attributes(&samplerate, &channels, &bits, &format);
const void* audio_out = _init_best_audio_out
(NULL, 0, samplerate, channels, format, 0);
if (!audio_out)
{
_mp_dll_close(mpctx);
FreeLibrary(hModule);
return 0;
}
char name[MAX_PATH] = {0};
char short_name[MAX_PATH] = {0};
char author[MAX_PATH] = {0};
char comment[MAX_PATH] = {0};
_mp_dll_audio_out_get_info(audio_out, name, short_name, author, comment);
_mp_dll_set_audio_out(mpctx, audio_out);
int bytes_to_write = 0;
int sleep_time = 0;
int outburst = channels * (bits >> 3) * 512;
int bps = channels * (bits >> 3) * samplerate;
char buf[MAX_OUTBURST] = {0};
int playsize = 0;
int a_out_buffer_len = 0;
char* a_out_buffer = NULL;
HANDLE hThread = INVALID_HANDLE_VALUE;
DWORD dwThreadID = 0;
callback_t callback = {0};
ao_infoex_t ao_infoex = {0};
DWORD start_count = 0, end_count = 0;
ao_infoex.sample_rate = samplerate;
ao_infoex.channels = channels;
ao_infoex.bits_per_sample = bits;
ao_infoex.frame_size = (channels == 1) ? 2 : 4;
ao_infoex.buffer_size = samplerate * ao_infoex.frame_size;
ao_infoex.funcs = (const ao_functions_t*)audio_out;
callback.data = &ao_infoex;
callback.data1 = CreateEvent(NULL, FALSE, FALSE, _T("WaitCompleteEvent"));
callback.data2 = CreateEvent(NULL, FALSE, FALSE, _T("GUIReadyEvent"));
hThread = CreateThread(NULL, 0, GUIThread, &callback, 0, &dwThreadID);
WaitForSingleObject(callback.data2, INFINITE);
start_count = GetTickCount();
while (!_mp_dll_get_eof(mpctx))
{
while (1)
{
bytes_to_write = _mp_dll_audio_out_get_space(audio_out);
if (bytes_to_write >= outburst)
break;
sleep_time = (outburst - bytes_to_write) * 1000 / bps;
if (sleep_time < 10)
sleep_time = 10;
_usec_sleep(sleep_time * 1000);
}
while (bytes_to_write)
{
int res = 0;
playsize = bytes_to_write;
if (playsize > MAX_OUTBURST)
playsize = MAX_OUTBURST;
bytes_to_write -= playsize;
res = _mp_decode_audio(sh_audio, playsize);
if (res < 0)
{
if (_mp_dll_audio_eof(mpctx))
_mp_dll_set_eof(mpctx, 1);
a_out_buffer_len =
_mp_dll_sh_audio_get_out_buffer_len(sh_audio);
if (a_out_buffer_len == 0)
break;
}
a_out_buffer_len =
_mp_dll_sh_audio_get_out_buffer_len(sh_audio);
if (playsize > a_out_buffer_len)
playsize = a_out_buffer_len;
a_out_buffer = _mp_dll_sh_audio_get_out_buffer(sh_audio);
playsize = _mp_dll_audio_out_play
(audio_out, a_out_buffer, playsize, 0);
spectrum_analyzer_writedata(handle, a_out_buffer, 0L,
(long)playsize);
if (playsize > 0)
{
a_out_buffer_len -= playsize;
memmove(a_out_buffer, &a_out_buffer[playsize],
a_out_buffer_len);
_mp_dll_sh_audio_set_out_buffer_len
(sh_audio, a_out_buffer_len);
}
end_count = GetTickCount();
if(end_count - start_count >= 25)
{
spectrum_analyzer_render(handle, 60);
start_count = end_count;
}
}
}
SendMessage(hMainWindow, WM_DESTROY, 0, 0);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
hThread = INVALID_HANDLE_VALUE;
CloseHandle(callback.data1);
callback.data1 = NULL;
CloseHandle(callback.data2);
callback.data2 = NULL;
_mp_dll_close(mpctx);
return 0;
}
History
- First release on 2011-12-12