本视频渲染器渲染RGB32视频流。内置音频渲染器,可渲染PCM音频流。音频渲染器包含采样率转换算法,可以接受任何采样率。也可隐藏视频渲染窗口,只作为音频渲染器使用。本渲染器是脱离DirectShow的视频渲染器,在应用程序中可能更会使用到。
使用方法
首先,加载本视频渲染器DLL,获得渲染器模块句柄。
cpp
HMODULE hVRender = LoadLibrary(L"VRender.dll");
定义媒体信息和样本信息结构,定义导出函数指针:
cpp
struct INFO//媒体信息
{
int VideoWidth = 0;//视频宽,单位像素
int VideoHeight = 0;//视频高,单位像素
LONGLONG FrameCur = 0;//帧持续时间,单位100纳秒
LONGLONG DUR = 0;//媒体时长,单位100纳秒
DWORD AudioSamplePerSec = 0;//音频样本采样率
DWORD StreamCount = 0;//流数量
};
struct SAMPLE_INFO//样本信息
{
BOOL B;//为TRUE,样本为第1个样本
DWORD STAR;//运行开始时间,单位毫秒
LONGLONG star;//样本开始时间,单位100纳秒
LONGLONG end;//样本结束时间,单位100纳秒
BYTE* pB;//样本缓冲区指针
int len;//样本的字节大小
HANDLE hRun;//"运行"事件句柄
HANDLE hSeek;//"定位"事件句柄
};
typedef HWND(__cdecl *MYPROC_VRender_Init)(INFO info, BOOL Show);
typedef int(__cdecl *MYPROC_VRender_Flush)();
typedef int(__cdecl *MYPROC_VRender_Redraw)();
typedef int(__cdecl *MYPROC_VRender_Exit)();
typedef int(__cdecl *MYPROC_VRender_FullScreen)(BOOL B);
typedef int(__cdecl *MYPROC_VRender_VSetParent)(BOOL B, HWND hParent, RECT rect);
typedef int(__cdecl *MYPROC_VRender_Show)(BOOL B);
typedef int(__cdecl *MYPROC_VRender_VideoRender)(SAMPLE_INFO info);
typedef int(__cdecl *MYPROC_VRender_AudioRender)(SAMPLE_INFO info);
获取导出函数的地址:
cpp
MYPROC_VRender_Init VRender_Init = NULL;
MYPROC_VRender_Flush VRender_Flush = NULL;
MYPROC_VRender_Redraw VRender_Redraw = NULL;
MYPROC_VRender_Exit VRender_Exit = NULL;
MYPROC_VRender_FullScreen VRender_FullScreen = NULL;
MYPROC_VRender_VSetParent VRender_VSetParent = NULL;
MYPROC_VRender_Show VRender_Show = NULL;
MYPROC_VRender_VideoRender VRender_VideoRender = NULL;
MYPROC_VRender_AudioRender VRender_AudioRender = NULL;
if (hVRender)
{
VRender_Init = (MYPROC_VRender_Init)GetProcAddress(hVRender, "Init");
VRender_Flush = (MYPROC_VRender_Flush)GetProcAddress(hVRender, "Flush");
VRender_Redraw = (MYPROC_VRender_Redraw)GetProcAddress(hVRender, "Redraw");
VRender_Exit = (MYPROC_VRender_Exit)GetProcAddress(hVRender, "Exit");
VRender_FullScreen = (MYPROC_VRender_FullScreen)GetProcAddress(hVRender, "FullScreen");
VRender_VSetParent = (MYPROC_VRender_VSetParent)GetProcAddress(hVRender, "VSetParent");
VRender_Show=(MYPROC_VRender_Show)GetProcAddress(hVRender, "Show");
VRender_VideoRender = (MYPROC_VRender_VideoRender)GetProcAddress(hVRender, "VideoRender");
VRender_AudioRender = (MYPROC_VRender_AudioRender)GetProcAddress(hVRender, "AudioRender");
}
使用INFO结构提供初始化参数,包括:视频的宽高,音频采样率;并指定最初是否显示渲染窗口,初始化本渲染器:
cpp
INFO info;
info.VideoWidth = 1200;
info.VideoHeight = 608;
info.AudioSamplePerSec = 44100;
if (VRender_Init != NULL)//如果地址不为空
VRender_Init(info, TRUE);
此时将创建视频渲染窗口,并初始化默认音频渲染端点。
使用样本信息结构SAMPLE_INFO提供样本信息,包括:
1.如果是第1帧,结构的B参数为TRUE,通知渲染器开始计时;(渲染器使用timeGetTime函数获取电脑开机以来的毫秒)。
2.媒体源的开始时间,单位毫秒。
3.提供"运行"和"定位"事件句柄。也可以不提供"定位"事件句柄,但必须提供"运行"事件句柄,通过检测"运行"事件信号,确定音频客户端是运行还是停止。DirectShow过滤器"定位"后会将样本时间戳从0开始,但本渲染器样本时间不是从0开始,仍使用样本在媒体中的实际时间。
4.提供样本的开始时间和结束时间。提供样本数据的缓冲区指针。样本的字节大小。视频样本是1帧RGB32的数据,音频样本大小必须小于1秒的数据量。
反复的调用视频渲染函数和音频渲染函数,样本数据将传递给渲染器,将获得视频图像和播放音频数据。如果渲染时间未到,函数将阻塞以等待渲染时间;如果已过样本的结束时间,将丢弃该样本,在渲染函数的下一次调用中,渲染下一个样本。
cpp
SAMPLE_INFO Vinfo;//样本信息结构已包含信息
VRender_VideoRender(Vinfo);
SAMPLE_INFO Ainfo;//样本信息结构已包含信息
VRender_AudioRender(Ainfo);
其他导出函数实现对渲染器窗口的操作和渲染器的销毁:
cpp
VRender_Flush();//丢弃音频渲染缓冲区的所有数据
VRender_Redraw();//重绘渲染器窗口
VRender_Exit();//销毁渲染器
VRender_FullScreen(TRUE);//参数TRUE,全屏渲染窗口。FALSE退出全屏
VRender_VSetParent(TRUE,hParent,rect);//参数1为TRUE,将渲染窗口设置为hParent的子窗口,rect指定窗口大小和位置。参数1为FALSE时,窗口恢复为弹出窗口;此时忽略参数2,3
VRender_Show(TRUE);//参数为TRUE,显示渲染窗口,FALSE隐藏渲染窗口
有关本视频渲染器的使用示例,可以看后续文章"Windows应用-播放视频"。
本视频渲染器DLL的全部代码
DLL.cpp
cpp
#include "windows.h"
#include "mmsystem.h"
#pragma comment(lib, "winmm")
#include "mmdeviceapi.h"
#include "audioclient.h"
#include "resource.h"
#define REFTIMES_PER_SEC 10000000
template <class T> void SafeRelease(T** ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
struct INFO//媒体信息
{
int VideoWidth = 0;//视频宽,单位像素
int VideoHeight = 0;//视频高,单位像素
LONGLONG FrameCur = 0;//帧持续时间,单位100纳秒
LONGLONG DUR = 0;//媒体时长,单位100纳秒
DWORD AudioSamplePerSec = 0;//音频样本采样率
DWORD StreamCount = 0;//流数量
};
struct SAMPLE_INFO//样本信息
{
BOOL B;//为TRUE,样本为第1个样本
DWORD STAR;//运行开始时间,单位毫秒
LONGLONG star;//样本开始时间,单位100纳秒
LONGLONG end;//样本结束时间,单位100纳秒
BYTE* pB;//样本缓冲区指针
int len;//样本的字节大小
HANDLE hRun;//"运行"事件句柄
HANDLE hSeek;//"定位"事件句柄
};
struct INITPARAM
{
UINT Width=640;
UINT Height=480;
BOOL Show=TRUE;
UINT AudioSamplePerSec = 0;
};
class VRender
{
public:
VRender()
{
hExit = CreateEvent(NULL, TRUE, FALSE, NULL);//手动重置,初始无信号
pAudioBuffer = new BYTE[48000 * 4];
if(S_OK != InitAudioClient())MessageBox(NULL, L"获取默认音频渲染端点失败", L"VRender", MB_OK);
}
~VRender()
{
delete[] pAudioBuffer; CloseHandle(hExit);
SafeRelease(&pAudioClient); SafeRelease(&pRenderClient);
}
INFO info;
HWND hwnd;//视频渲染窗口句柄
HDC hDC;
HDC hComDC;
HBRUSH hBrush = NULL;
BYTE* pAudioBuffer = NULL;//音频"采样率转换"目标缓冲区指针
int index = 0; short LLast = 0, RLast = 0;
int WriteOutput(UINT DSamples, UINT SSamples, short F, short N, BYTE*& pD, BOOL BL);
int Convert(UINT DSamples, BYTE* pD, int& len, UINT SSamples, BYTE* pS, int Len);//转换采样率
UINT AudioSamplePerSec = 0;//输入音频采样率
IAudioClient* pAudioClient = NULL;//默认音频渲染端点客户端接口
IAudioRenderClient *pRenderClient = NULL; //音频服务接口
WAVEFORMATEX wfx;
UINT32 bufferFrameCount;//申请的端点缓冲区总大小,单位音频帧
HRESULT InitAudioClient();//初始化默认音频渲染端点
LONGLONG STAR;//视频流开始时间,单位毫秒
LONGLONG ASTAR;//音频流开始时间,单位毫秒
RECT SRect, DRect;//源矩形,目标矩形
UINT cbBuffer;//视频渲染缓冲区大小
HBITMAP hBitmap;
int PreWindowState = -1;//先前的窗口状态
int WindowState = -1;//标记窗口状态;1弹出窗口,2子窗口,3全屏幕窗口
HWND hParent;//记录作为子窗口时,父窗口句柄
RECT ChildRect;//记录作为子窗口时,窗口大小和位置
HANDLE hExit = NULL;//"退出"事件句柄
};
VRender* pVRender = NULL;//VRender对象指针
HRESULT VRender::InitAudioClient()//初始化默认音频渲染端点
{
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
IMMDeviceEnumerator* pEnumerator = NULL;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if (hr != S_OK)
{
MessageBox(NULL, L"创建设备枚举器失败", L"VRender", MB_OK); return hr;
}
IMMDevice* pDevice = NULL;
hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
if (hr != S_OK)
{
MessageBox(NULL, L"获取默认音频渲染端点失败", L"VRender", MB_OK); SafeRelease(&pEnumerator); return hr;
}
hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient);
if (hr != S_OK)
{
MessageBox(NULL, L"获取音频渲染端点客户端失败", L"VRender", MB_OK); SafeRelease(&pEnumerator); SafeRelease(&pDevice); return hr;
}
wfx.wFormatTag = 1;//默认音频渲染端点只接受PCM或FLOAT格式
wfx.nChannels = 2;
wfx.nSamplesPerSec = 48000;//默认音频渲染端点只接受48000采样率
wfx.nAvgBytesPerSec = 48000 * 4;
wfx.nBlockAlign = 4;
wfx.wBitsPerSample = 16;
wfx.cbSize = 0;
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, hnsRequestedDuration, 0, &wfx, NULL);//创建端点缓冲区,可以容纳1秒的音频数据
if (hr != S_OK)
{
MessageBox(NULL, L"音频客户端初始化失败!", L"VRender", MB_OK); SafeRelease(&pEnumerator); SafeRelease(&pDevice); SafeRelease(&pAudioClient); return hr;
}
hr = pAudioClient->GetBufferSize(&bufferFrameCount);//获取申请的端点缓冲区总大小,单位音频帧
if (hr != S_OK)
{
MessageBox(NULL, L"获取缓冲区大小失败!", L"VRender", MB_OK); SafeRelease(&pEnumerator); SafeRelease(&pDevice); SafeRelease(&pAudioClient); return hr;
}
hr = pAudioClient->GetService(__uuidof(IAudioRenderClient), (void**)&pRenderClient);//获取音频服务
if (hr != S_OK)
{
MessageBox(NULL, L"音频服务没有打开!", L"VRender", MB_OK); SafeRelease(&pEnumerator); SafeRelease(&pDevice); SafeRelease(&pAudioClient); return hr;
}
SafeRelease(&pEnumerator); SafeRelease(&pDevice);
return hr;
}
void PopupWindow(HWND hwnd)//更改为弹出窗口
{
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_APPWINDOW);//指定窗口扩展样式
SetWindowLong(hwnd, GWL_STYLE, WS_POPUP | WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);//指定窗口样式
int ScreenWidth = GetSystemMetrics(SM_CXSCREEN);//获取主显示器的宽度,以像素为单位
int ScreenHeight = GetSystemMetrics(SM_CYSCREEN);//获取主显示器的高度,以像素为单位
RECT rect = pVRender->SRect;
AdjustWindowRectEx(&rect, WS_POPUP | WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, FALSE, WS_EX_APPWINDOW); // 根据客户区大小计算窗口大小
SetParent(pVRender->hwnd, NULL);//指定无父窗口
SetWindowPos(pVRender->hwnd, HWND_NOTOPMOST, (ScreenWidth - (rect.right - rect.left)) / 2, (ScreenHeight - (rect.bottom - rect.top)) / 2, rect.right - rect.left, rect.bottom - rect.top, SWP_SHOWWINDOW);
pVRender->PreWindowState = pVRender->WindowState;//记录先前的状态
pVRender->WindowState = 1;//设置窗口状态标志
}
void ChildWindow(HWND hwnd, HWND hParent, RECT rect)//更改为子窗口
{
pVRender->hParent = hParent; pVRender->ChildRect = rect;//记录父窗口句柄,子窗口大小和位置
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_TOOLWINDOW);//指定窗口扩展样式
SetWindowLong(hwnd, GWL_STYLE, WS_VISIBLE | WS_CHILD);//更改窗口样式为子窗口
SetParent(hwnd, hParent);//指定父窗口
MoveWindow(hwnd, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, 0);//设置窗口大小
ShowWindow(hwnd, SW_SHOW);//显示窗口
pVRender->PreWindowState = pVRender->WindowState;//记录先前的状态
pVRender->WindowState = 2;//设置窗口状态标志
}
void FullScreen(HWND hwnd)//更改为全屏窗口
{
SetWindowLong(hwnd, GWL_EXSTYLE, 0);//指定窗口扩展样式
SetWindowLong(hwnd, GWL_STYLE, WS_VISIBLE | WS_POPUP);//更改窗口样式
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
SetParent(pVRender->hwnd, NULL);//指定无父窗口
SetWindowPos(pVRender->hwnd, HWND_TOPMOST, 0, 0, screenWidth, screenHeight, SWP_SHOWWINDOW);//设置窗口为始终在顶层,大小为整个屏幕
pVRender->PreWindowState = pVRender->WindowState;//记录先前的状态
pVRender->WindowState = 3;//设置窗口状态标志
}
void VSetDRect(HWND hwnd, RECT rect)//按原始图像高宽比设置目标矩形
{
RECT r; r.left = 0; r.top = 0; r.right = rect.right - rect.left; r.bottom = rect.bottom - rect.top;
double Db = (double)r.bottom / (double)r.right; double Sb = (double)pVRender->SRect.bottom / (double)pVRender->SRect.right;
if (Db >= Sb)//如果渲染窗口客户区高宽比大
{
pVRender->DRect.left =0; pVRender->DRect.right = r.right;
int n = (int)(((double)r.bottom - (double)r.right * Sb) / 2);
pVRender->DRect.top = n; pVRender->DRect.bottom = r.bottom - n;
}
else
{
pVRender->DRect.top = 0; pVRender->DRect.bottom = r.bottom;
int m = (int)(((double)r.right - (double)r.bottom / Sb) / 2);
pVRender->DRect.left = m; pVRender->DRect.right = r.right - m;
}
RedrawWindow(hwnd, NULL, NULL, RDW_INTERNALPAINT);
}
LRESULT CALLBACK VRenderWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)//视频渲染窗口过程函数
{
RECT rect; LONG Style = 0, ExStyle = 0;
switch (uMsg)
{
case WM_SIZE://窗口大小改变
GetClientRect(hwnd, &rect);
VSetDRect(hwnd, rect);//改变渲染窗口大小时,不更改图像宽高比
break;
case WM_KEYDOWN://按下某个键
if (VK_ESCAPE == wParam)//收到ESC键,退出全屏
{
if (pVRender->WindowState==3)//如果窗口为全屏幕
{
if (pVRender->PreWindowState == 2)//如果先前的状态为子窗口
{
ChildWindow(pVRender->hwnd, pVRender->hParent, pVRender->ChildRect);
}
else
{
PopupWindow(pVRender->hwnd);
}
}
}
break;
case WM_PAINT:
GetClientRect(pVRender->hwnd, &rect);
FillRect(pVRender->hDC, &rect, pVRender->hBrush);
break;
case WM_CLOSE://点击了窗口关闭按钮
ShowWindow(hwnd, SW_HIDE);//隐藏窗口
return TRUE;//不调用DefWindowProc,防止销毁窗口
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
DWORD WINAPI WindowThread(LPVOID pParam)
{
VRender vRender;
pVRender = &vRender;
INITPARAM* pwh = (INITPARAM*)pParam;
pVRender->SRect.left = 0; pVRender->SRect.top = 0; pVRender->SRect.right = pwh->Width; pVRender->SRect.bottom = pwh->Height;
pVRender->cbBuffer = pwh->Width * pwh->Height * 4;
pVRender->AudioSamplePerSec = pwh->AudioSamplePerSec;
HMODULE hModule = GetModuleHandle(L"VRender");
WNDCLASSEX wcx;
wcx.cbSize = sizeof(wcx);//结构的大小
wcx.style = CS_HREDRAW | CS_VREDRAW; //窗口类样式
wcx.lpfnWndProc = VRenderWndProc; //指向窗口过程
wcx.cbClsExtra = 0; //没有额外的类内存
wcx.cbWndExtra = 0; //没有额外的窗口内存
wcx.hInstance = hModule; //实例句柄
HICON hIcon = (HICON)LoadImage(hModule, MAKEINTRESOURCE(IDI_ICON1), IMAGE_ICON, 48, 48, 0);
wcx.hIcon = hIcon; //指定图标
wcx.hCursor = LoadCursor(NULL, IDC_ARROW); //预定义的光标
pVRender->hBrush= CreateSolidBrush(RGB(0, 0, 0));
wcx.hbrBackground = pVRender->hBrush;//背景笔刷
wcx.lpszMenuName =NULL; //菜单资源名称
wcx.lpszClassName = L"VRenderWindow"; //窗口类的名称
wcx.hIconSm = hIcon;//小图标
RegisterClassEx(&wcx);//注册窗口类
int ScreenWidth = GetSystemMetrics(SM_CXSCREEN);//获取主显示器的宽度,以像素为单位
int ScreenHeight = GetSystemMetrics(SM_CYSCREEN);//获取主显示器的高度,以像素为单位
RECT rect = pVRender->DRect = pVRender->SRect;//赋值目标矩形
int Width, Height;//窗口的宽高
if (pwh->Show)//如果要求窗口最初可见,创建弹出窗口
{
AdjustWindowRectEx(&rect, WS_POPUP | WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, FALSE, WS_EX_APPWINDOW);//根据客户区大小计算窗口大小
Width = rect.right - rect.left; Height = rect.bottom - rect.top;//窗口的宽高
pVRender->hwnd = CreateWindowEx(//创建"视频渲染窗口"
WS_EX_APPWINDOW, //窗口扩展样式
L"VRenderWindow", //类名
L"VRender", //窗口名称
WS_POPUP | WS_VISIBLE | WS_BORDER | WS_CAPTION | WS_SIZEBOX | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, //窗口样式
(ScreenWidth - Width) / 2, //水平居中
(ScreenHeight - Height) / 2, //垂直居中
Width, //宽度
Height, //高度
NULL, //没有父窗口或所有者窗口
NULL, //使用的菜单
hModule, //实例句柄
NULL); //没有窗口创建数据
pVRender->WindowState = 1;
}
else//如果要求窗口最初不可见
{
AdjustWindowRectEx(&rect, WS_POPUP, FALSE, WS_EX_TOOLWINDOW);
Width = rect.right - rect.left; Height = rect.bottom - rect.top;//窗口的宽高
pVRender->hwnd = CreateWindowEx(
WS_EX_TOOLWINDOW,
L"VRenderWindow",
L"VRender",
WS_POPUP,
(ScreenWidth - Width) / 2, //水平居中
(ScreenHeight - Height) / 2, //垂直居中
Width, //宽度
Height, //高度
NULL,
NULL,
hModule,
NULL
);
pVRender->WindowState = 2;
}
pVRender->hDC = GetDC(pVRender->hwnd);//获取渲染窗口DC
pVRender->hComDC = CreateCompatibleDC(pVRender->hDC);//创建兼容DC
pVRender->hBitmap = CreateBitmap(pVRender->DRect.right - pVRender->DRect.left, pVRender->DRect.bottom - pVRender->DRect.top, 1, 32, NULL);//创建32位位图
SelectObject(pVRender->hComDC, pVRender->hBitmap);//选择位图到兼容DC
SetStretchBltMode(pVRender->hDC, COLORONCOLOR);//设置拉伸绘制模式
MSG msg;
BOOL fGotMessage;
while ((fGotMessage = GetMessage(&msg, pVRender->hwnd, 0, 0)) != 0 && fGotMessage != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ReleaseDC(pVRender->hwnd, pVRender->hDC); DeleteObject(pVRender->hBitmap); DeleteDC(pVRender->hComDC); pVRender = NULL;
return 1;
}
int VRender::WriteOutput(UINT DSamples, UINT SSamples, short F, short N, BYTE*& pD, BOOL BL)//参数1目标采样率,参数2源采样率,参数3左值,参数4右值,参数5目标缓冲区指针,参数6TRUE左声道,FALSE右声道
{
int Count = 0;
double Star = (double)index / (double)SSamples;//起始时间
double End = (double)(index + 1) / (double)SSamples;//结束时间
int DIndex = (int)(Star * (double)DSamples);//计算目标起始索引
Agan:
double Dx = (double)DIndex / (double)DSamples;//计算目标时间
if (Dx < Star)
{
DIndex++;
goto Agan;
}
if (Dx >= End)
{
if (BL)//如果写左声道值
{
if (Count == 1)
{
pD -= 2;
}
else if (Count>1)
{
pD -= Count * 4 - 2;
}
return Count * 2;
}
else//如果写右声道值
{
if (Count >= 1)
{
pD -= 2;
}
return Count * 2;
}
}
Count++; DIndex++;
short sh = (short)((double)F + (double)(N - F)*(Dx - Star) / (End - Star));
CopyMemory(pD, &sh, 2); pD += 4;
goto Agan;
}
int VRender::Convert(UINT DSamples, BYTE* pD, int& len, UINT SSamples, BYTE* pS, int Len)//参数1目标采样率,参数2目标缓冲区,参数3累计目标的字节大小,参数4源采样率,参数5源缓冲区指针,参数6源缓冲区大小
{
BYTE* pDD = pD;
int Count = Len / 4;//获取单个样本的数量
for (int i = 0; i < Count; i++)
{
short LFirst, RFirst, LNext, RNext;
if (i == 0)//如果是第1个样本
{
if (index == 0)
{
LFirst = *((short*)(pS + i * 4)); RFirst = *((short*)(pS + i * 4 + 2)); LNext = *((short*)(pS + (i + 1) * 4)); RNext = *((short*)(pS + (i + 1) * 4 + 2));
len += WriteOutput(DSamples, SSamples, LFirst, LNext, pDD, TRUE); len += WriteOutput(DSamples, SSamples, RFirst, RNext, pDD, FALSE);
}
else
{
LFirst = LLast; RFirst = RLast; LNext = *((short*)(pS + i * 4)); RNext = *((short*)(pS + i * 4));
len += WriteOutput(DSamples, SSamples, LFirst, LNext, pDD, TRUE); len += WriteOutput(DSamples, SSamples, RFirst, RNext, pDD, FALSE);
LFirst = *((short*)(pS + i * 4)); RFirst = *((short*)(pS + i * 4 + 2)); LNext = *((short*)(pS + (i + 1) * 4)); RNext = *((short*)(pS + (i + 1) * 4 + 2));
len += WriteOutput(DSamples, SSamples, LFirst, LNext, pDD, TRUE); len += WriteOutput(DSamples, SSamples, RFirst, RNext, pDD, FALSE);
}
}
else if (i == Count - 1)//如果是最后1个样本
{
LLast = *((short*)(pS + i * 4)); RLast = *((short*)(pS + i * 4 + 2));
}
else
{
LFirst = *((short*)(pS + i * 4)); RFirst = *((short*)(pS + i * 4 + 2)); LNext = *((short*)(pS + (i + 1) * 4)); RNext = *((short*)(pS + (i + 1) * 4 + 2));
len += WriteOutput(DSamples, SSamples, LFirst, LNext, pDD, TRUE); len += WriteOutput(DSamples, SSamples, RFirst, RNext, pDD, FALSE);
}
index++;
}
return 0;
}
//下面是导出函数定义
#ifdef __cplusplus // If used by C++ code,
extern "C" { // we need to export the C interface
#endif
__declspec(dllexport) HWND __cdecl Init(INFO info, BOOL Show)//初始化
{
if (pVRender != NULL)//如果线程正在运行,不再创建新的线程,只更新参数
{
pVRender->info = info;
pVRender->SRect.left = 0; pVRender->SRect.top = 0; pVRender->SRect.right = info.VideoWidth; pVRender->SRect.bottom = info.VideoHeight;
pVRender->DRect = pVRender->SRect;
pVRender->cbBuffer = info.VideoWidth * info.VideoHeight * 4;
pVRender->AudioSamplePerSec= info.AudioSamplePerSec;
DeleteDC(pVRender->hComDC);
pVRender->hComDC = CreateCompatibleDC(pVRender->hDC);//创建兼容DC
DeleteObject(pVRender->hBitmap);
pVRender->hBitmap = CreateBitmap(pVRender->DRect.right - pVRender->DRect.left, pVRender->DRect.bottom - pVRender->DRect.top, 1, 32, NULL);//创建32位位图
SelectObject(pVRender->hComDC, pVRender->hBitmap);//选择位图到兼容DC
SetStretchBltMode(pVRender->hDC, COLORONCOLOR);//设置拉伸绘制模式
return pVRender->hwnd;
}
INITPARAM wh; wh.Width = info.VideoWidth; wh.Height = info.VideoHeight; wh.Show = Show; wh.AudioSamplePerSec = info.AudioSamplePerSec;
CreateThread(NULL, 0, WindowThread, &wh, 0, NULL);
Sleep(1000);//等待窗口创建完成
return pVRender->hwnd;
}
__declspec(dllexport) int __cdecl Flush()//刷新
{
if (pVRender)
{
HRESULT hr = pVRender->pAudioClient->Reset();
return 0;
}
return 1;
}
__declspec(dllexport) int __cdecl Redraw()//重绘渲染窗口
{
if (pVRender)
{
RedrawWindow(pVRender->hwnd, NULL, NULL, RDW_INTERNALPAINT);
return 0;
}
return 1;
}
__declspec(dllexport) int __cdecl Exit(void)//退出
{
if (pVRender)
{
SetEvent(pVRender->hExit);//设置"退出"有信号
HRESULT hr = pVRender->pAudioClient->Stop();
PostMessage(pVRender->hwnd, WM_QUIT, 0, 0);//发送WM_QUIT消息,用于退出渲染窗口线程
Sleep(500);//等待窗口线程退出
UnregisterClass(L"VRenderWindow", GetModuleHandle(L"VRender"));//删除窗口类注册
return 0;
}
return 1;
}
__declspec(dllexport) int __cdecl FullScreen(BOOL B)//B为TRUE时,指定全屏播放模式;FALSE退出全屏
{
if (pVRender)
{
if (B)
{
FullScreen(pVRender->hwnd); return 0;
}
else
{
PopupWindow(pVRender->hwnd); return 0;
}
}
return 1;
}
__declspec(dllexport) int __cdecl VSetParent(BOOL B, HWND hParent, RECT rect)//参数1为TRUE时,为播放窗口指定父窗口,并指定播放窗口大小;参数1为FALSE时,窗口恢复为弹出窗口
{
if (pVRender)
{
if (B)
{
ChildWindow(pVRender->hwnd, hParent, rect);//更改为子窗口
return 0;
}
else
{
PopupWindow(pVRender->hwnd);//更改为弹出窗口
return 0;
}
}
return 1;
}
__declspec(dllexport) int __cdecl Show(BOOL B)//B为TRUE时,显示渲染窗口;FALSE隐藏渲染窗口
{
if (pVRender)
{
if (B)
{
ShowWindow(pVRender->hwnd, SW_SHOW); return 0;
}
else
{
ShowWindow(pVRender->hwnd, SW_HIDE); return 0;
}
}
return 1;
}
__declspec(dllexport) int __cdecl VideoRender(SAMPLE_INFO info)//视频渲染函数
{
Agan:
if (info.hSeek)
{
DWORD mSeek = WaitForSingleObject(info.hSeek, 0);//检测"定位信号",不等待
if (mSeek == WAIT_OBJECT_0)//如果正在定位
{
return 1;//解除阻塞
}
}
if (info.hRun)
{
DWORD mRun = WaitForSingleObject(info.hRun, 0);//检测"运行",不等待
if (mRun != WAIT_OBJECT_0)//如果已暂停或停止
{
return 1;//解除阻塞
}
}
DWORD mExit = WaitForSingleObject(pVRender->hExit, 0);//检测"退出",不等待
if (mExit == WAIT_OBJECT_0)//如果有"退出"信号
{
return 1;//解除阻塞
}
if (info.B)//如果是此次运行的第1帧
{
pVRender->STAR = (LONGLONG)info.STAR - info.star / 10000;
}
if ((LONGLONG)timeGetTime() - pVRender->STAR > info.end / 10000)//如果当前时间超过帧渲染结束时间,跳过该帧
return 1;
if ((LONGLONG)timeGetTime() - pVRender->STAR < info.star / 10000)//如果当前时间小于帧渲染开始时间,等待
goto Agan;
SetBitmapBits(pVRender->hBitmap, pVRender->cbBuffer, info.pB);
StretchBlt(pVRender->hDC, pVRender->DRect.left, pVRender->DRect.top, pVRender->DRect.right - pVRender->DRect.left, pVRender->DRect.bottom - pVRender->DRect.top,
pVRender->hComDC, pVRender->SRect.left, pVRender->SRect.top, pVRender->SRect.right - pVRender->SRect.left, pVRender->SRect.bottom - pVRender->SRect.top, SRCCOPY);
return 0;
}
__declspec(dllexport) int __cdecl AudioRender(SAMPLE_INFO info)//音频渲染函数
{
HRESULT hr;
Agan:
if (info.hSeek)
{
DWORD mSeek = WaitForSingleObject(info.hSeek, 0);//检测"定位信号",不等待
if (mSeek == WAIT_OBJECT_0)//如果正在定位
{
return 1;//解除阻塞
}
}
if (info.hRun)
{
DWORD mRun = WaitForSingleObject(info.hRun, 0);//检测"运行",不等待
if (mRun != WAIT_OBJECT_0)//如果已暂停或停止
{
hr = pVRender->pAudioClient->Stop(); //停止音频流
return 1;//解除阻塞
}
if (info.B)//如果是此次运行的第1个样本
{
if (mRun == WAIT_OBJECT_0)//如果正在运行
{
hr = pVRender->pAudioClient->Reset();//刷新所有挂起的数据
hr = pVRender->pAudioClient->Start();//启动音频流
}
}
}
DWORD mExit = WaitForSingleObject(pVRender->hExit, 0);//检测"退出",不等待
if (mExit == WAIT_OBJECT_0)//如果有"退出"信号
{
return 1;//解除阻塞
}
if (info.B)//如果是此次运行的第1个样本
{
pVRender->ASTAR = (LONGLONG)info.STAR - info.star / 10000;
}
if ((LONGLONG)timeGetTime() - pVRender->ASTAR > info.end / 10000)//如果当前时间超过帧渲染结束时间,跳过该帧
return 1;
if ((LONGLONG)timeGetTime() - pVRender->ASTAR < info.star / 10000)//如果当前时间小于帧渲染开始时间,等待
goto Agan;
BYTE* pB = NULL; int len = 0;
if (pVRender->AudioSamplePerSec != 48000)//如果采样率不是48000
{
if (info.B)pVRender->index = 0;
pB = pVRender->pAudioBuffer;
pVRender->Convert(48000, pB, len, pVRender->AudioSamplePerSec, info.pB, info.len);//将采样率转换为48000
}
else//采样率是48000
{
pB = info.pB; len = info.len;
}
BYTE* pData = NULL; DWORD flags = 0;
UINT32 numFramesPadding, numFramesAvailable;
hr = pVRender->pAudioClient->GetCurrentPadding(&numFramesPadding);//获取未播放的音频帧数
numFramesAvailable = pVRender->bufferFrameCount - numFramesPadding;//计算缓冲区中可填充的帧数
if ((long)(numFramesAvailable * pVRender->wfx.nBlockAlign) < len)//如果可填充的大小,小于传递的有效数据的大小
{
Sleep(10); goto Agan;//等待并阻塞
}
hr = pVRender->pRenderClient->GetBuffer((UINT32)(len / pVRender->wfx.nBlockAlign), &pData);
CopyMemory(pData, pB, len);
hr = pVRender->pRenderClient->ReleaseBuffer(len / pVRender->wfx.nBlockAlign, flags);//释放GetBuffer获取的缓冲区
return 0;
}
#ifdef __cplusplus
}
#endif