mirror of
https://github.com/cookiengineer/audacity
synced 2025-04-30 07:39:42 +02:00
1235 lines
27 KiB
C++
1235 lines
27 KiB
C++
/*
|
|
* PortBurn
|
|
* Windows XP IMAPI implementation
|
|
*
|
|
* Authors:
|
|
* Dominic Mazzoni, Leland Lucius
|
|
*
|
|
* The following MSDN page was used as a guide:
|
|
* http://msdn2.microsoft.com/en-us/library/aa366236.aspx
|
|
*
|
|
* License: LGPL
|
|
*/
|
|
|
|
#include "portburn.h"
|
|
|
|
#define _WIN32_WINNT 0x0400
|
|
|
|
#include <windows.h>
|
|
#include <tchar.h>
|
|
#include <malloc.h>
|
|
#include <imapi.h>
|
|
#include <imapierror.h>
|
|
#include <stdio.h>
|
|
|
|
#define DEBUG(a) printf a
|
|
|
|
typedef struct {
|
|
|
|
// Used for duration of portburn open session
|
|
|
|
IGlobalInterfaceTable *pGit; // Things that must be cleaned up at close
|
|
IDiscMaster *pDiscMaster; //
|
|
DWORD dwDiscMaster; //
|
|
IRedbookDiscMaster *pRedbookDiscMaster; //
|
|
IEnumDiscRecorders *pEnumDiscRecorders; //
|
|
HRESULT hres;
|
|
bool verify;
|
|
bool test;
|
|
bool underrun;
|
|
bool eject;
|
|
bool gapless;
|
|
int speed;
|
|
|
|
// Used for duration of device open session
|
|
|
|
IDiscRecorder *pDiscRecorder; // Things that must be cleaned up at close
|
|
IDiscMasterProgressEvents *pEvents; //
|
|
HANDLE hThread; //
|
|
UINT_PTR pnCookie; //
|
|
float fraction;
|
|
bool erasetype;
|
|
bool burning;
|
|
bool erasing;
|
|
bool cancel;
|
|
} PBHandlev1;
|
|
|
|
class PortBurnProgress : public IDiscMasterProgressEvents
|
|
{
|
|
public:
|
|
static HRESULT STDMETHODCALLTYPE
|
|
CreateInstance(void *handle, IDiscMasterProgressEvents** ppEvents);
|
|
|
|
private:
|
|
PortBurnProgress(void *handle);
|
|
|
|
PBHandlev1 *h;
|
|
|
|
protected:
|
|
STDMETHOD(QueryInterface)(REFIID riid, LPVOID* ppv);
|
|
STDMETHOD_(ULONG, AddRef)(VOID);
|
|
STDMETHOD_(ULONG, Release)(VOID);
|
|
|
|
protected:
|
|
STDMETHOD(QueryCancel)(boolean *pbCancel);
|
|
STDMETHOD(NotifyPnPActivity)(VOID);
|
|
STDMETHOD(NotifyAddProgress)(LONG nCompletedSteps, LONG nTotalSteps);
|
|
STDMETHOD(NotifyBlockProgress)(LONG nCompleted, LONG nTotal);
|
|
STDMETHOD(NotifyTrackProgress)(LONG nCurrentTrack, LONG nTotalTracks);
|
|
STDMETHOD(NotifyPreparingBurn)(LONG nEstimatedSeconds);
|
|
STDMETHOD(NotifyClosingDisc)(LONG nEstimatedSeconds);
|
|
STDMETHOD(NotifyBurnComplete)(HRESULT status);
|
|
STDMETHOD(NotifyEraseComplete)(HRESULT status);
|
|
|
|
private:
|
|
ULONG m_cRefs;
|
|
};
|
|
|
|
PortBurnProgress::PortBurnProgress(void *handle)
|
|
{
|
|
h = (PBHandlev1 *) handle;
|
|
m_cRefs = 0;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
PortBurnProgress::CreateInstance(void *handle, IDiscMasterProgressEvents **ppEvents)
|
|
{
|
|
if (ppEvents == NULL) {
|
|
return E_POINTER;
|
|
}
|
|
|
|
PortBurnProgress *pThis = new PortBurnProgress(handle);
|
|
if (pThis == NULL) {
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
return pThis->QueryInterface(IID_IDiscMasterProgressEvents,
|
|
(VOID**) ppEvents);
|
|
}
|
|
|
|
STDMETHODIMP
|
|
PortBurnProgress::QueryInterface(REFIID riid, LPVOID* ppv)
|
|
{
|
|
if (ppv == NULL) {
|
|
return E_POINTER;
|
|
}
|
|
|
|
*ppv = NULL;
|
|
|
|
if ((riid != IID_IUnknown) && (riid != IID_IDiscMasterProgressEvents)) {
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
*ppv = this;
|
|
AddRef();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
PortBurnProgress::AddRef(VOID)
|
|
{
|
|
return InterlockedIncrement((LONG*) &m_cRefs);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG)
|
|
PortBurnProgress::Release(VOID)
|
|
{
|
|
ULONG cRef = InterlockedDecrement((LONG*) &m_cRefs);
|
|
|
|
if (cRef == 0) {
|
|
delete this;
|
|
}
|
|
|
|
return cRef;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::QueryCancel(boolean *pbCancel)
|
|
{
|
|
DEBUG(("QueryCancel() -> %d\n", h->cancel));
|
|
|
|
if (pbCancel != NULL) {
|
|
*pbCancel = h->cancel;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyPnPActivity(VOID)
|
|
{
|
|
DEBUG(("NotifyPnActivity\n"));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyAddProgress(LONG nCompletedSteps, LONG nTotalSteps)
|
|
{
|
|
DEBUG(("NotifyAddProgress %d %d\n", nCompletedSteps, nTotalSteps));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyBlockProgress(LONG nCompleted, LONG nTotal)
|
|
{
|
|
DEBUG(("NotifyBlockProgress %d %d\n", nCompleted, nTotal));
|
|
|
|
/* This should never actually reach 1.0; we wait until the BurnComplete
|
|
message for a 1.0 */
|
|
h->fraction = nCompleted / (float) (nTotal + 1);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyTrackProgress(LONG nCurrentTrack, LONG nTotalTracks)
|
|
{
|
|
DEBUG(("NotifyTrackProgress %d %d\n", nCurrentTrack, nTotalTracks));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyPreparingBurn(LONG nEstimatedSeconds)
|
|
{
|
|
DEBUG(("NotifyPreparingBurn %d\n", nEstimatedSeconds));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyClosingDisc(LONG nEstimatedSeconds)
|
|
{
|
|
DEBUG(("NotifyClosingDisc %d\n", nEstimatedSeconds));
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyBurnComplete(HRESULT status)
|
|
{
|
|
DEBUG(("NotifyBurnComplete %08x\n", status));
|
|
|
|
h->fraction = 1.0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP_(HRESULT)
|
|
PortBurnProgress::NotifyEraseComplete(HRESULT status)
|
|
{
|
|
DEBUG(("NotifyEraseComplete %08x\n", status));
|
|
|
|
h->fraction = 1.0;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/* Recording Thread */
|
|
static DWORD WINAPI PortBurn_v1_RecordDisc(LPVOID lpParam)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *)lpParam;
|
|
IDiscMaster *pDiscMaster;
|
|
|
|
h->hres = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
|
|
if (SUCCEEDED(h->hres)) {
|
|
h->hres = h->pGit->GetInterfaceFromGlobal(h->dwDiscMaster,
|
|
IID_IDiscMaster,
|
|
(void**) &pDiscMaster);
|
|
if (SUCCEEDED(h->hres)) {
|
|
h->cancel = false;
|
|
h->fraction = 0.0;
|
|
|
|
h->hres = pDiscMaster->RecordDisc(h->test,
|
|
h->eject);
|
|
|
|
pDiscMaster->Release();
|
|
}
|
|
|
|
CoUninitialize();
|
|
}
|
|
|
|
h->fraction = 1.0;
|
|
|
|
if (FAILED(h->hres)) {
|
|
return pbErrBurnFailed;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Erasing Thread */
|
|
static DWORD WINAPI PortBurn_v1_EraseDisc(LPVOID lpParam)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *)lpParam;
|
|
IDiscMaster *pDiscMaster;
|
|
IDiscRecorder *pDiscRecorder;
|
|
|
|
h->hres = CoInitializeEx(0, COINIT_APARTMENTTHREADED);
|
|
if (SUCCEEDED(h->hres)) {
|
|
h->hres = h->pGit->GetInterfaceFromGlobal(h->dwDiscMaster,
|
|
IID_IDiscMaster,
|
|
(void**) &pDiscMaster);
|
|
if (SUCCEEDED(h->hres)) {
|
|
h->hres = pDiscMaster->GetActiveDiscRecorder(&pDiscRecorder);
|
|
if (SUCCEEDED(h->hres)) {
|
|
h->cancel = false;
|
|
h->fraction = 0.99f; // No erase progress in v1
|
|
|
|
h->hres = pDiscRecorder->OpenExclusive();
|
|
if (SUCCEEDED(h->hres)) {
|
|
h->hres = pDiscRecorder->Erase(h->erasetype);
|
|
pDiscRecorder->Close();
|
|
}
|
|
|
|
pDiscRecorder->Release();
|
|
}
|
|
}
|
|
|
|
pDiscMaster->Release();
|
|
|
|
CoUninitialize();
|
|
}
|
|
|
|
h->fraction = 1.0;
|
|
|
|
if (FAILED(h->hres)) {
|
|
return pbErrEraseFailed;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* */
|
|
static HRESULT MasterTerm(PBHandlev1 *h)
|
|
{
|
|
if (h->pEnumDiscRecorders != NULL) {
|
|
h->pEnumDiscRecorders->Release();
|
|
h->pEnumDiscRecorders = NULL;
|
|
}
|
|
|
|
if (h->pRedbookDiscMaster != NULL) {
|
|
h->pRedbookDiscMaster->Release();
|
|
h->pRedbookDiscMaster = NULL;
|
|
}
|
|
|
|
h->pDiscMaster->Close();
|
|
|
|
return h->hres;
|
|
}
|
|
|
|
/* */
|
|
static HRESULT MasterInit(PBHandlev1 *h)
|
|
{
|
|
if (h->pRedbookDiscMaster != NULL) {
|
|
MasterTerm(h);
|
|
}
|
|
|
|
h->hres = h->pDiscMaster->Open();
|
|
if (FAILED(h->hres)) {
|
|
return S_OK;
|
|
}
|
|
|
|
h->hres = h->pDiscMaster->
|
|
SetActiveDiscMasterFormat(IID_IRedbookDiscMaster,
|
|
(void **)&h->pRedbookDiscMaster);
|
|
|
|
if (FAILED(h->hres)) {
|
|
return MasterTerm(h);
|
|
}
|
|
|
|
h->hres = h->pDiscMaster->EnumDiscRecorders(&h->pEnumDiscRecorders);
|
|
if (FAILED(h->hres)) {
|
|
return MasterTerm(h);
|
|
}
|
|
|
|
if (h->pDiscRecorder != NULL) {
|
|
h->hres = h->pDiscMaster->SetActiveDiscRecorder(h->pDiscRecorder);
|
|
if (FAILED(h->hres)) {
|
|
return MasterTerm(h);
|
|
}
|
|
}
|
|
|
|
return h->hres;
|
|
}
|
|
void PortBurn_v1_Close(void *handle);
|
|
void *PortBurn_v1_Open()
|
|
{
|
|
PBHandlev1 *h;
|
|
|
|
#if defined(_DEBUG)
|
|
AllocConsole();
|
|
freopen("CONOUT$", "w", stdout);
|
|
#endif
|
|
|
|
h = (PBHandlev1 *) HeapAlloc(GetProcessHeap(),
|
|
HEAP_ZERO_MEMORY,
|
|
sizeof(PBHandlev1));
|
|
if (h == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
h->dwDiscMaster = -1;
|
|
|
|
h->test = pbTestDefault;
|
|
h->verify = pbVerifyDefault;
|
|
h->underrun = pbUnderrunDefault;
|
|
h->eject = pbEjectDefault;
|
|
h->gapless = pbGaplessDefault;
|
|
h->speed = pbSpeedDefault;
|
|
|
|
h->hres = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
|
|
NULL,
|
|
CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARGS(&h->pGit));
|
|
if (FAILED(h->hres)) {
|
|
PortBurn_v1_Close(h);
|
|
return NULL;
|
|
}
|
|
|
|
h->hres = CoCreateInstance(__uuidof(MSDiscMasterObj),
|
|
NULL,
|
|
CLSCTX_ALL,
|
|
IID_PPV_ARGS(&h->pDiscMaster));
|
|
if (FAILED(h->hres)) {
|
|
PortBurn_v1_Close(h);
|
|
return NULL;
|
|
}
|
|
|
|
h->hres = h->pGit->RegisterInterfaceInGlobal(h->pDiscMaster,
|
|
IID_IDiscMaster,
|
|
&h->dwDiscMaster);
|
|
if (FAILED(h->hres)) {
|
|
PortBurn_v1_Close(h);
|
|
return NULL;
|
|
}
|
|
|
|
h->hres = MasterInit(h);
|
|
if (FAILED(h->hres)) {
|
|
PortBurn_v1_Close(h);
|
|
return NULL;
|
|
}
|
|
|
|
return h;
|
|
}
|
|
|
|
/* Cleanup */
|
|
void PortBurn_v1_Close(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return;
|
|
}
|
|
|
|
MasterTerm(h);
|
|
|
|
if (h->dwDiscMaster != -1) {
|
|
h->pGit->RevokeInterfaceFromGlobal(h->dwDiscMaster);
|
|
}
|
|
|
|
if (h->pDiscMaster != NULL) {
|
|
h->pDiscMaster->Release();
|
|
}
|
|
|
|
if (h->pGit != NULL) {
|
|
h->pGit->Release();
|
|
}
|
|
|
|
HeapFree(GetProcessHeap(), 0, h);
|
|
}
|
|
|
|
/* Return a human-readable error string for the last operating system
|
|
specific error (NOT human readable strings for the PortBurn error
|
|
codes). Caller should dispose of the returned string using free(). */
|
|
char *PortBurn_v1_LastError(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
HRESULT hr;
|
|
LPTSTR windowsErrorString = NULL;
|
|
char *errorString = NULL;
|
|
int len;
|
|
|
|
if (h == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Have Windows allocate a buffer for us and format the error
|
|
message in windowsErrorString */
|
|
hr = h->hres;
|
|
if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS) {
|
|
hr = HRESULT_CODE(hr);
|
|
}
|
|
|
|
len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) &windowsErrorString, 0, NULL);
|
|
if (windowsErrorString == NULL) {
|
|
windowsErrorString = (LPTSTR) LocalAlloc(LPTR, 128 * sizeof(TCHAR));
|
|
if (windowsErrorString == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
len = _stprintf_s(windowsErrorString, 128, L"HRESULT = %08x", hr);
|
|
}
|
|
|
|
/* Convert the string */
|
|
errorString = (char *) malloc(len + 1);
|
|
if (errorString != NULL) {
|
|
errorString[0] = '\0';
|
|
WideCharToMultiByte(CP_ACP, 0, windowsErrorString, len, errorString, len, NULL, NULL);
|
|
errorString[len] = '\0';
|
|
}
|
|
|
|
LocalFree(windowsErrorString);
|
|
|
|
return errorString;
|
|
}
|
|
|
|
/* Get the number of devices capable of burning audio CDs.
|
|
If the result is N, then calls to GetDeviceName and OpenDevice
|
|
are guaranteed to be valid for indices from 0 up to N-1, until
|
|
the next time you call GetNumDevices. At that point, the list of
|
|
devices will be rescanned, and may be different. */
|
|
int PortBurn_v1_GetNumDevices(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
IDiscRecorder *pDiscRecorder;
|
|
ULONG fetched;
|
|
int count = 0;
|
|
|
|
if (h == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if (h->pEnumDiscRecorders != NULL) {
|
|
h->pEnumDiscRecorders->Release();
|
|
h->pEnumDiscRecorders = NULL;
|
|
}
|
|
|
|
h->hres = h->pDiscMaster->EnumDiscRecorders(&h->pEnumDiscRecorders);
|
|
if (FAILED(h->hres)) {
|
|
return 0;
|
|
}
|
|
|
|
while (h->pEnumDiscRecorders->Next(1, &pDiscRecorder, &fetched) == S_OK) {
|
|
pDiscRecorder->Release();
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/* Get the name of the device with a given index. Only valid
|
|
after a call to GetNumDevices. */
|
|
char *PortBurn_v1_GetDeviceName(void *handle, int index)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
IDiscRecorder *pDiscRecorder;
|
|
BSTR bVendor = NULL;
|
|
BSTR bProduct = NULL;
|
|
BSTR bRevision = NULL;
|
|
ULONG fetched;
|
|
TCHAR *wname;
|
|
char *name;
|
|
int len;
|
|
|
|
if (h == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (index < 0 || index >= PortBurn_v1_GetNumDevices(h)) {
|
|
h->hres = E_INVALIDARG;
|
|
return NULL;
|
|
}
|
|
|
|
h->pEnumDiscRecorders->Reset();
|
|
if (index != 0) {
|
|
h->pEnumDiscRecorders->Skip(index - 1);
|
|
}
|
|
|
|
h->hres = h->pEnumDiscRecorders->Next(1, &pDiscRecorder, &fetched);
|
|
if (FAILED(h->hres)) {
|
|
return NULL;
|
|
}
|
|
|
|
h->hres = pDiscRecorder->GetDisplayNames(&bVendor, &bProduct, &bRevision);
|
|
|
|
pDiscRecorder->Release();
|
|
|
|
if (FAILED(h->hres)) {
|
|
return NULL;
|
|
}
|
|
|
|
len = SysStringLen(bVendor) + 1 +
|
|
SysStringLen(bProduct) + 1 +
|
|
SysStringLen(bRevision);
|
|
|
|
wname = (LPWSTR)alloca((len + 1) * sizeof(TCHAR));
|
|
|
|
_stprintf_s(wname,
|
|
(len + 1),
|
|
_T("%s %s %s"),
|
|
(LPCTSTR) bVendor,
|
|
(LPCTSTR) bProduct,
|
|
(LPCTSTR) bRevision);
|
|
|
|
SysFreeString(bVendor);
|
|
SysFreeString(bProduct);
|
|
SysFreeString(bRevision);
|
|
|
|
name = (char *) malloc(len + 1);
|
|
if (name == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
name[0] = '\0';
|
|
WideCharToMultiByte(CP_ACP, 0, wname, len, name, len, NULL, NULL);
|
|
name[len] = '\0';
|
|
|
|
return name;
|
|
}
|
|
|
|
/* Open a particular device by index number. Returns 0 on success;
|
|
any nonzero value indicates an error, for example if the device is
|
|
already open by some other program. */
|
|
int PortBurn_v1_OpenDevice(void *handle, int index)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
ULONG fetched;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder != NULL) {
|
|
h->hres = E_POINTER;
|
|
return pbErrDeviceAlreadyOpen;
|
|
}
|
|
|
|
if (index < 0 || index >= PortBurn_v1_GetNumDevices(h)) {
|
|
h->hres = E_INVALIDARG;
|
|
return pbErrInvalidDeviceIndex;
|
|
}
|
|
|
|
h->pEnumDiscRecorders->Reset();
|
|
if (index != 0) {
|
|
h->pEnumDiscRecorders->Skip(index - 1);
|
|
}
|
|
|
|
h->hres = h->pEnumDiscRecorders->Next(1, &h->pDiscRecorder, &fetched);
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotAccessDevice;
|
|
}
|
|
|
|
h->hres = h->pDiscMaster->SetActiveDiscRecorder(h->pDiscRecorder);
|
|
if (FAILED(h->hres)) {
|
|
h->pDiscRecorder->Release();
|
|
h->pDiscRecorder = NULL;
|
|
return pbErrCannotReserveDevice;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Close a device */
|
|
int PortBurn_v1_CloseDevice(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->hThread != NULL) {
|
|
h->cancel = true;
|
|
WaitForSingleObject(h->hThread, INFINITE);
|
|
CloseHandle(h->hThread);
|
|
h->hThread = NULL;
|
|
}
|
|
|
|
if (h->pnCookie != NULL) {
|
|
h->pDiscMaster->ProgressUnadvise(h->pnCookie);
|
|
h->pnCookie = NULL;
|
|
}
|
|
|
|
if (h->pEvents != NULL) {
|
|
h->pEvents->Release();
|
|
h->pEvents = NULL;
|
|
}
|
|
|
|
if (h->pDiscRecorder != NULL) {
|
|
h->pDiscRecorder->Close();
|
|
h->pDiscRecorder->Release();
|
|
h->pDiscRecorder = NULL;
|
|
}
|
|
|
|
h->burning = false;
|
|
h->erasing = false;
|
|
h->cancel = false;
|
|
h->fraction = 0.0;
|
|
|
|
h->test = pbTestDefault;
|
|
h->verify = pbVerifyDefault;
|
|
h->underrun = pbUnderrunDefault;
|
|
h->eject = pbEjectDefault;
|
|
h->gapless = pbGaplessDefault;
|
|
h->speed = pbSpeedDefault;
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Eject the media in the currently opened device */
|
|
int PortBurn_v1_EjectDevice(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
h->hres = h->pDiscRecorder->OpenExclusive();
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotReserveDevice;
|
|
}
|
|
|
|
h->hres = h->pDiscRecorder->Eject();
|
|
|
|
h->pDiscRecorder->Close();
|
|
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotEject;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* This indicates you're ready to start staging audio data for the
|
|
currently opened device. At this point you are committing to
|
|
exclusive access to the CD burner, and this is the function that
|
|
will fail if another program is using the device, or if there is
|
|
no writable CD media in the device at this point. */
|
|
int PortBurn_v1_StartStaging(void *handle, const char *tmpdir)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
if (h->pEvents != NULL) {
|
|
return pbErrAlreadyStagingOrBurning;
|
|
}
|
|
|
|
h->hres = PortBurnProgress::CreateInstance(h, &h->pEvents);
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotPrepareToBurn;
|
|
}
|
|
|
|
h->hres = h->pDiscMaster->ProgressAdvise(h->pEvents,
|
|
&h->pnCookie);
|
|
if (FAILED(h->hres)) {
|
|
h->pEvents->Release();
|
|
h->pEvents = NULL;
|
|
return pbErrCannotPrepareToBurn;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Start a new audio track. Pass the name of the track, and the
|
|
length in CD Audio frames (each frame is 1/75.0 of a second, exactly). */
|
|
int PortBurn_v1_StartTrack(void *handle, const char *name)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
// this fails if the disc isn't writable...
|
|
h->hres = h->pRedbookDiscMaster->CreateAudioTrack(0);
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotStageTrack;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Add one frame of audio to the current track. The buffer must be exactly
|
|
1176 elements long, containing interleaved left and right audio samples.
|
|
The values should be signed 16-bit numbers in the native endianness of
|
|
this computer. */
|
|
int PortBurn_v1_AddFrame(void *handle, short *buffer)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
int oneBlockByteCount = 1176 * 2;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
h->hres = h->pRedbookDiscMaster->AddAudioTrackBlocks((byte *)buffer,
|
|
oneBlockByteCount);
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotWriteToStagingFile;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Finish the current audio track. */
|
|
int PortBurn_v1_EndTrack(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
h->hres = h->pRedbookDiscMaster->CloseAudioTrack();
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotUseStagedFileForBurning;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Begin burning the disc. */
|
|
int PortBurn_v1_StartBurning(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
IPropertyStorage *prop;
|
|
PROPSPEC spec[30];
|
|
PROPVARIANT var[30];
|
|
DWORD dwID;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
h->hres = h->pDiscRecorder->GetRecorderProperties(&prop);
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotStartBurning;
|
|
}
|
|
|
|
spec[0].ulKind = PRSPEC_LPWSTR;
|
|
spec[0].lpwstr = L"EnableBufferUnderrunFree";
|
|
var[0].vt = VT_I4;
|
|
var[0].lVal = h->underrun;
|
|
|
|
spec[1].ulKind = PRSPEC_LPWSTR;
|
|
spec[1].lpwstr = L"AudioGapSize";
|
|
var[1].vt = VT_I4;
|
|
var[1].lVal = h->gapless ? 0 : 150;
|
|
|
|
spec[2].ulKind = PRSPEC_LPWSTR;
|
|
spec[2].lpwstr = L"WriteSpeed";
|
|
var[2].vt = VT_I4;
|
|
var[2].lVal = h->speed;
|
|
|
|
h->hres = prop->WriteMultiple(3, spec, var, PID_FIRST_USABLE);
|
|
|
|
prop->Release();
|
|
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotStartBurning;
|
|
}
|
|
|
|
h->burning = true;
|
|
|
|
h->hThread = CreateThread(NULL,
|
|
0,
|
|
PortBurn_v1_RecordDisc,
|
|
h,
|
|
0,
|
|
&dwID);
|
|
if (h->hThread == NULL) {
|
|
h->burning = false;
|
|
return pbErrCannotStartBurning;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Cancel if burning was in progress. It might take a while for
|
|
this to take effect; wait until GetStatus says 1.0 to close
|
|
the device. */
|
|
int PortBurn_v1_CancelBurning(void *handle)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
float frac = 0.0;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
if (h->burning == false) {
|
|
return pbErrNotCurrentlyBurning;
|
|
}
|
|
|
|
h->cancel = true;
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* During burning, returns the fraction complete in the given
|
|
float pointer, from 0.0 to 1.0. If this function returns
|
|
nonzero, the disc burning has failed and should be aborted.
|
|
If *out_fraction_complete is equal to 1.0, the burning is done;
|
|
you can call PortBurn_CloseDevice.
|
|
*/
|
|
int PortBurn_v1_GetStatus(void *handle, float *out_fraction_complete)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
if (h->burning == false) {
|
|
return pbErrNotCurrentlyBurning;
|
|
}
|
|
|
|
MSG msg;
|
|
if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
WaitForSingleObject(h->hThread, 100);
|
|
|
|
*out_fraction_complete = h->fraction;
|
|
|
|
if (h->fraction == 1.0) {
|
|
h->burning = false;
|
|
h->cancel = true;
|
|
WaitForSingleObject(h->hThread, INFINITE);
|
|
CloseHandle(h->hThread);
|
|
h->hThread = NULL;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* Get option value. */
|
|
int PortBurn_v1_GetOption(void *handle, int option, int *value)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
int ret = pbSuccess;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
switch (option)
|
|
{
|
|
case pbOptTest:
|
|
{
|
|
*value = h->test;
|
|
}
|
|
break;
|
|
|
|
case pbOptVerify:
|
|
{
|
|
*value = h->verify;
|
|
}
|
|
break;
|
|
|
|
case pbOptUnderrun:
|
|
{
|
|
*value = h->underrun;
|
|
}
|
|
break;
|
|
|
|
case pbOptEject:
|
|
{
|
|
*value = h->eject;
|
|
}
|
|
break;
|
|
|
|
case pbOptGapless:
|
|
{
|
|
*value = h->gapless;
|
|
}
|
|
break;
|
|
|
|
case pbOptSpeed:
|
|
{
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
ret = pbErrInvalidOption;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int PortBurn_v1_SetOption(void *handle, int option, int value)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
int ret = pbSuccess;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
switch (option)
|
|
{
|
|
case pbOptTest:
|
|
{
|
|
h->test = value != 0;
|
|
}
|
|
break;
|
|
|
|
case pbOptVerify:
|
|
{
|
|
h->verify = value != 0;
|
|
}
|
|
break;
|
|
|
|
case pbOptUnderrun:
|
|
{
|
|
h->underrun = value != 0;
|
|
}
|
|
break;
|
|
|
|
case pbOptEject:
|
|
{
|
|
h->eject = value != 0;
|
|
}
|
|
break;
|
|
|
|
case pbOptGapless:
|
|
{
|
|
h->gapless = value != 0;
|
|
}
|
|
break;
|
|
|
|
case pbOptSpeed:
|
|
{
|
|
h->speed = value;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
{
|
|
ret = pbErrInvalidOption;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Erase the media in the currently opened device */
|
|
int PortBurn_v1_StartErasing(void *handle, int type)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
DWORD dwID;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
if (h->hThread != NULL) {
|
|
return pbErrCannotStartErasing;
|
|
}
|
|
|
|
h->erasetype = (type == pbEraseQuick ? false : true);
|
|
|
|
h->erasing = true;
|
|
|
|
h->hThread = CreateThread(NULL,
|
|
0,
|
|
PortBurn_v1_EraseDisc,
|
|
h,
|
|
0,
|
|
&dwID);
|
|
if (h->hThread == NULL) {
|
|
h->erasing = false;
|
|
return pbErrCannotStartErasing;
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
int PortBurn_v1_GetEraseStatus(void *handle, float *out_fraction_complete)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
if (h->erasing == false) {
|
|
return pbErrNotCurrentlyErasing;
|
|
}
|
|
|
|
WaitForSingleObject(h->hThread, 100);
|
|
|
|
*out_fraction_complete = h->fraction;
|
|
|
|
if (h->fraction == 1.0) {
|
|
h->erasing = false;
|
|
WaitForSingleObject(h->hThread, INFINITE);
|
|
CloseHandle(h->hThread);
|
|
h->hThread = NULL;
|
|
|
|
h->hres = MasterInit(h);
|
|
if (FAILED(h->hres)) {
|
|
return pbErrEraseFailed;
|
|
}
|
|
}
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
/* */
|
|
int PortBurn_v1_GetMediaState(void *handle, int *state)
|
|
{
|
|
PBHandlev1 *h = (PBHandlev1 *) handle;
|
|
BYTE sessions;
|
|
BYTE last;
|
|
ULONG start;
|
|
ULONG next;
|
|
ULONG free;
|
|
LONG type;
|
|
LONG flags;
|
|
int mstate;
|
|
|
|
if (h == NULL) {
|
|
return pbErrNoHandle;
|
|
}
|
|
|
|
h->hres = S_OK;
|
|
|
|
if (h->pDiscRecorder == NULL) {
|
|
return pbErrDeviceNotOpen;
|
|
}
|
|
|
|
h->hres = h->pDiscRecorder->OpenExclusive();
|
|
if (FAILED(h->hres)) {
|
|
return pbErrCannotReserveDevice;
|
|
}
|
|
|
|
// Only doing this to detect empty drive
|
|
h->hres = h->pDiscRecorder->QueryMediaInfo(&sessions, &last, &start, &next, &free);
|
|
if (h->hres == IMAPI_E_MEDIUM_NOTPRESENT) {
|
|
h->pDiscRecorder->Close();
|
|
*state = pbMediaNone;
|
|
return pbSuccess;
|
|
}
|
|
|
|
h->hres = h->pDiscRecorder->QueryMediaType(&type, &flags);
|
|
|
|
h->pDiscRecorder->Close();
|
|
|
|
if (h->hres == IMAPI_E_MEDIUM_NOTPRESENT) {
|
|
*state = pbMediaNone;
|
|
return pbSuccess;
|
|
}
|
|
else if (FAILED(h->hres)) {
|
|
return pbErrCannotAccessDevice;
|
|
}
|
|
|
|
mstate = 0;
|
|
|
|
if (flags & MEDIA_BLANK) {
|
|
mstate |= pbMediaBlank;
|
|
}
|
|
|
|
if (flags & MEDIA_RW) {
|
|
mstate |= pbMediaErasable;
|
|
}
|
|
|
|
if (flags & MEDIA_WRITABLE) {
|
|
mstate |= pbMediaAppendable;
|
|
mstate |= pbMediaOverwritable;
|
|
}
|
|
|
|
*state = mstate;
|
|
|
|
return pbSuccess;
|
|
}
|
|
|
|
int PortBurn_v1_GetSupportedSpeeds(void *handle, int *cnt, int *speeds[])
|
|
{
|
|
*cnt = 0;
|
|
*speeds = NULL;
|
|
return pbErrNoHandle;
|
|
}
|