Skip to main content
Topic: Trying to compile ASIO output plugin and... (Read 2366 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Trying to compile ASIO output plugin and...

Dear all,

I currently try to convert and compile ASIO source code from Otachan with ASIOSDK2.3 and latest Foobar SDK.
I almost manage to make it works.

However, I don't undertstand how to register the output service with the current SDK.
I currently have the code below and I have the following errors in VS console.
Would a nice soul do help me ?

Regards.

Code: [Select]
1>------ Build started: Project: foo_output_asio, Configuration: Debug Win32 ------
1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V140\Microsoft.CppBuild.targets(936,5): warning MSB8027: Two or more files with the name of stdafx.cpp will produce outputs to the same location. This can lead to an incorrect build result.  The files involved are ..\..\pfc\stdafx.cpp, ..\..\SDK\stdafx.cpp.
1>  foo_output_asio.cpp
1>c:\sdk\fb2k\foo_output_asio(dll)_051\shared\fb2kdebug.h(80): warning C4838: conversion from 'int' to 'const ULONG_PTR' requires a narrowing conversion
1>c:\sdk\fb2k\sdk\output.h(175): error C2039: 'g_advanced_settings_query': is not a member of 'output_asio'
1>  c:\sdk\fb2k\foo_output_asio(dll)_051\source\foo_output_asio.h(56): note: see declaration of 'output_asio'
1>  c:\sdk\fb2k\sdk\output.h(173): note: while compiling class template member function 't_uint32 output_entry_impl_t<T,output_entry>::get_config_flags(void)'
1>          with
1>          [
1>              T=output_asio
1>          ]
1>  c:\sdk\fb2k\sdk\service.h(620): note: see reference to class template instantiation 'output_entry_impl_t<T,output_entry>' being compiled
1>          with
1>          [
1>              T=output_asio
1>          ]
1>  c:\sdk\fb2k\sdk\output.h(186): note: see reference to class template instantiation 'service_factory_single_t<output_entry_impl_t<T,output_entry>>' being compiled
1>          with
1>          [
1>              T=output_asio
1>          ]
1>  c:\sdk\fb2k\foo_output_asio(dll)_051\source\foo_output_asio.cpp(25): note: see reference to class template instantiation 'output_factory_t<output_asio>' being compiled
1>c:\sdk\fb2k\sdk\output.h(175): error C3861: 'g_advanced_settings_query': identifier not found
1>c:\sdk\fb2k\sdk\output.h(176): error C2039: 'g_needs_bitdepth_config': is not a member of 'output_asio'
1>  c:\sdk\fb2k\foo_output_asio(dll)_051\source\foo_output_asio.h(56): note: see declaration of 'output_asio'
1>c:\sdk\fb2k\sdk\output.h(176): error C3861: 'g_needs_bitdepth_config': identifier not found
1>c:\sdk\fb2k\sdk\output.h(177): error C2039: 'g_needs_dither_config': is not a member of 'output_asio'
1>  c:\sdk\fb2k\foo_output_asio(dll)_051\source\foo_output_asio.h(56): note: see declaration of 'output_asio'
1>c:\sdk\fb2k\sdk\output.h(177): error C3861: 'g_needs_dither_config': identifier not found
1>c:\sdk\fb2k\sdk\output.h(178): error C2039: 'g_needs_device_list_prefixes': is not a member of 'output_asio'
1>  c:\sdk\fb2k\foo_output_asio(dll)_051\source\foo_output_asio.h(56): note: see declaration of 'output_asio'
1>c:\sdk\fb2k\sdk\output.h(178): error C3861: 'g_needs_device_list_prefixes': identifier not found

Code: [Select]
/*
** foo_output_asio - ASIO output for foobar2000
**
** 2006/2/25 Written by Otachan
** http://otachan.com/
*/

#define STRICT

#include <windows.h>
#include <process.h>

#include "foo_output_asio.h"
#include "pcmasio.h"

extern HINSTANCE WSLhInstance;

extern AsioDrivers* asioDrivers;

//service_factory_single_t<initquit, initquit_output_asio> foo;
//service_factory_t<output, output_asio> foo2;

initquit_factory_t<initquit_output_asio> foo;
output_factory_t<output_asio> foo2;

//service_factory_single_t<initquit_output_asio> foo;
//service_factory_t<output_asio> foo2;

DECLARE_COMPONENT_VERSION(
NAME,
VER,
NAME " v" VER "\n\n"
"Copyright (C) 2003-2006 Otachan\n"
"http://otachan.com/\n\n"
"ASIO Technology by Steinberg.");

// {38B5B07D-532B-472B-B406-A484630A5021}
static const GUID guid_cfg_device =
{ 0x38b5b07d, 0x532b, 0x472b,{ 0xb4, 0x6, 0xa4, 0x84, 0x63, 0xa, 0x50, 0x21 } };

// {1D2540D2-5941-4787-AA60-E756ABCF32E5}
static const GUID guid_cfg_thread_priority =
{ 0x1d2540d2, 0x5941, 0x4787,{ 0xaa, 0x60, 0xe7, 0x56, 0xab, 0xcf, 0x32, 0xe5 } };

// {FD8C6045-CF61-4188-A0EA-12BB9D69062F}
static const GUID guid_cfg_buffer_size =
{ 0xfd8c6045, 0xcf61, 0x4188,{ 0xa0, 0xea, 0x12, 0xbb, 0x9d, 0x69, 0x6, 0x2f } };

// {8F295CD2-55AD-4771-95CA-3242871D5A4C}
static const GUID guid_cfg_shift_channels =
{ 0x8f295cd2, 0x55ad, 0x4771,{ 0x95, 0xca, 0x32, 0x42, 0x87, 0x1d, 0x5a, 0x4c } };

// {9E3A57CF-5D71-4667-AAFF-2F6E95212612}
static const GUID guid_direct_input_monitor =
{ 0x9e3a57cf, 0x5d71, 0x4667,{ 0xaa, 0xff, 0x2f, 0x6e, 0x95, 0x21, 0x26, 0x12 } };


cfg_int cfg_device(guid_cfg_device, 0);
cfg_int cfg_thread_priority(guid_cfg_thread_priority, 3);
cfg_int cfg_buffer_size(guid_cfg_buffer_size, 7);
cfg_int cfg_shift_channels(guid_cfg_shift_channels, 0);
cfg_int cfg_direct_input_monitor(guid_direct_input_monitor, 0);

CRITICAL_SECTION CriticalSectionInit;

PcmAsio* pPcmAsio;

HANDLE hThread;
HANDLE EventReadyThread;
HANDLE EventDestroyThread;

bool InitDLL;

BOOL WINAPI
DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID /*lpvReserved*/)
{
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
::InitializeCriticalSection(&CriticalSectionInit);

WSLhInstance = hinstDLL;

InitDLL = false;
break;
case DLL_PROCESS_DETACH:
::DeleteCriticalSection(&CriticalSectionInit);
break;
}

return true;
}

unsigned int __stdcall
ThreadProc(void* /*Param*/)
{
asioDrivers = new AsioDrivers;
pPcmAsio = new PcmAsio;

::SetEvent(EventReadyThread);

while(::WaitForSingleObjectEx(EventDestroyThread, INFINITE, true) != WAIT_OBJECT_0);

delete pPcmAsio;
delete asioDrivers;

_endthreadex(0);

return 0;
}

void CALLBACK
ApcProc(ULONG_PTR dwParam)
{
ParamMsg* const Param = reinterpret_cast<ParamMsg*>(dwParam);

switch(Param->Msg) {
case MSG_CLOSE_DRIVER:
pPcmAsio->CloseDriver();
break;
case MSG_OPEN:
Param->RetMsg = pPcmAsio->MsgOpen(Param->Param1, Param->Param2, Param->Param3, Param->Param4);
break;
case MSG_CAN_WRITE:
Param->RetMsg = pPcmAsio->MsgCanWrite();
break;
case MSG_WRITE:
Param->RetMsg = pPcmAsio->MsgWrite(Param->Param1, Param->Buff);
break;
case MSG_PAUSE:
pPcmAsio->MsgPause(Param->Param1);
break;
case MSG_PLAY:
pPcmAsio->MsgPlay();
break;
case MSG_FLUSH:
Param->RetMsg = pPcmAsio->MsgFlush();
break;
}

Param->UnPause();
}

ParamMsg::ParamMsg(int _Msg)
{
Msg = _Msg;
}

ParamMsg::ParamMsg(int _Msg, int _Param1)
{
Msg = _Msg;
Param1 = _Param1;
}

ParamMsg::ParamMsg(int _Msg, int _Param1, int _Param2, int _Param3, int _Param4)
{
Msg = _Msg;
Param1 = _Param1;
Param2 = _Param2;
Param3 = _Param3;
Param4 = _Param4;
}

ParamMsg::ParamMsg(int _Msg, int _Param1, unsigned char* _Buff)
{
Msg = _Msg;
Param1 = _Param1;
Buff = _Buff;
}

int
ParamMsg::Call(void)
{
EventWaitThread = ::CreateEvent(NULL, false, false, NULL);

::QueueUserAPC(&ApcProc, hThread, reinterpret_cast<ULONG_PTR>(this));
::WaitForSingleObject(EventWaitThread, INFINITE);

::CloseHandle(EventWaitThread);

return RetMsg;
}

void
ParamMsg::UnPause(void)
{
::SetEvent(EventWaitThread);
}

void
Init(void)
{
::EnterCriticalSection(&CriticalSectionInit);

if(InitDLL == false) {
EventDestroyThread = ::CreateEvent(NULL, false, false, NULL);
EventReadyThread = ::CreateEvent(NULL, false, false, NULL);

unsigned int dwThread;

hThread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, ThreadProc, NULL, 0, &dwThread));

::WaitForSingleObject(EventReadyThread, INFINITE);

::CloseHandle(EventReadyThread);

InitDLL = true;
}

::LeaveCriticalSection(&CriticalSectionInit);
}

inline void
Quit(void)
{
::SetEvent(EventDestroyThread);

if(::WaitForSingleObject(hThread, 5000) != WAIT_OBJECT_0) {
if(::TerminateThread(hThread, 0)) {
::WaitForSingleObject(hThread, 3000);
}
}

::CloseHandle(hThread);
::CloseHandle(EventDestroyThread);
}


void
initquit_output_asio::on_init(void)
{
Init();
}

void
initquit_output_asio::on_quit(void)
{
Quit();
}

// ********************************** /
// output_asio
//

output_asio::output_asio(void)
{
Init();
OpenDriver = false;
}

output_asio::~output_asio(void)
{
if(OpenDriver) ParamMsg(MSG_CLOSE_DRIVER).Call();
}

int
output_asio::open_ex(int srate, int bps, int nch, int format_code)
{
const int RetCode = ParamMsg(MSG_OPEN, srate, format_code, bps, nch).Call();
OpenDriver = RetCode != 0;
return RetCode;
}

int
output_asio::can_write(void)
{
return ParamMsg(MSG_CAN_WRITE).Call();
}

int
output_asio::write(const char* data, int bytes)
{
return ParamMsg(MSG_WRITE, bytes,
reinterpret_cast<unsigned char*>(const_cast<char*>(data))).Call();
}

int
output_asio::get_latency_bytes(void)
{
return pPcmAsio->MsgGetLatency();
}

void
output_asio::pause(int state)
{
ParamMsg(MSG_PAUSE, state).Call();
}

void
output_asio::force_play(void)
{
ParamMsg(MSG_PLAY).Call();
}

int
output_asio::do_flush(void)
{
return ParamMsg(MSG_FLUSH).Call();
}

int
output_asio::is_playing(void)
{
return !!pPcmAsio->MsgGetLatency();
}

Re: Trying to compile ASIO output plugin and...

Reply #1
Comment prepending class output_entry_impl_t from SDK/output.h states that "output_entry methods are forwarded to static methods of your output class". And in its code you can find calls like
Code: [Select]
if (T::g_advanced_settings_query()) flags |= output_entry::flag_needs_advanced_config;
'T' there is your output_asio class. In other words, output_asio implementation should contain static methods like
Code: [Select]
class output_asio {
public:
  static bool g_advanced_settings_query() { ... }
  static bool g_needs_bitdepth_config() { ... }
  // etc.

Re: Trying to compile ASIO output plugin and...

Reply #2
Hi,

Thank you for your answer. It was done in the meantime.
I think i'm not so far from the goal.

However when I try to register the class, I have these errors, and my current code is below.
It speaks about a reinterpret_cast, but i'm surprised to use that to be able to register the output.
Any idea ?

Regards.

Code: [Select]
c:\sdk\fb2k\pfc\primitives.h(186): error C2440: 'return': cannot convert from 'service_impl_t<output_asio_instance> *' to 'output::t_interface *'
1>  c:\sdk\fb2k\pfc\primitives.h(186): note: Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
1>  c:\sdk\fb2k\sdk\service.h(82): note: see reference to function template instantiation 't_ret *pfc::safe_ptr_cast<T,t_source>(t_param *)' being compiled
1>          with
1>          [
1>              t_ret=output::t_interface,
1>              T=output::t_interface,
1>              t_source=service_impl_t<output_asio_instance>,
1>              t_param=service_impl_t<output_asio_instance>
1>          ]

Code: [Select]
// ********************************** /
// class output_asio_instance
//

class output_asio_instance : public output_entry_asio
{
public:


output_asio_instance(const GUID & p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth)
{
Init();
OpenDriver = false;
}

~output_asio_instance(void)
{
if (OpenDriver) ParamMsg(MSG_CLOSE_DRIVER).Call();
}

static void g_advanced_settings_popup(HWND p_parent, POINT p_menupoint) {};
static int g_advanced_settings_query() { return output_entry::flag_needs_advanced_config; };
static int g_needs_dither_config() { return output_entry::flag_needs_dither_config; };
static int g_needs_bitdepth_config() { return output_entry::flag_needs_bitdepth_config; };
static int g_needs_device_list_prefixes() { return output_entry::flag_needs_device_list_prefixes; };
static const GUID g_get_guid(void)
{
// {3A5EDE8E-840D-497c-9774-156A12FC4275}
static const GUID guid =
{ 0x3a5ede8e, 0x840d, 0x497c,{ 0x97, 0x74, 0x15, 0x6a, 0x12, 0xfc, 0x42, 0x75 } };

return guid;
}

static const char* g_get_name(void)
{
return NAME2;
}

virtual const char* get_config_page_name(void)
{
return NAME2;
}

void instantiate(service_ptr_t<output> & p_out, const GUID & p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth) {

p_out = new service_impl_t<output_asio_instance>(p_device, p_buffer_length, p_dither, p_bitdepth);
};

void enum_devices(output_device_enum_callback &) {};
GUID get_guid() { g_get_guid(); };
const char *get_name(void) { g_get_name(); };
void advanced_settings_popup(HWND, POINT) {};
t_uint32 output_entry::get_config_flags(void) {};

void
output_asio_instance::open(const t_samplespec &) {};
void
output_asio_instance::pause(bool) {};
void
output_asio_instance::on_flush(void) {};
void
output_asio_instance::volume_set(double) {};
void
output_asio_instance::on_update(void) {};
void
output_asio_instance::write(const audio_chunk &) {};
t_size
output_asio_instance::can_write_samples(void) {
t_size val;
return val;
};
t_size
output_asio_instance::get_latency_samples(void) {
t_size val;
return val;
};


int
output_asio_instance::open_ex(int srate, int bps, int nch, int format_code)
{
const int RetCode = ParamMsg(MSG_OPEN, srate, format_code, bps, nch).Call();
OpenDriver = RetCode != 0;
return RetCode;
}

int
output_asio_instance::can_write(void)
{
return ParamMsg(MSG_CAN_WRITE).Call();
}

int
output_asio_instance::write(const char* data, int bytes)
{
return ParamMsg(MSG_WRITE, bytes,
reinterpret_cast<unsigned char*>(const_cast<char*>(data))).Call();
}

int
output_asio_instance::get_latency_bytes(void)
{
return pPcmAsio->MsgGetLatency();
}

void
output_asio_instance::pause(int state)
{
ParamMsg(MSG_PAUSE, state).Call();
}

void
output_asio_instance::force_play(void)
{
ParamMsg(MSG_PLAY).Call();
}

int
output_asio_instance::do_flush(void)
{
return ParamMsg(MSG_FLUSH).Call();
}

int
output_asio_instance::is_playing(void)
{
return !!pPcmAsio->MsgGetLatency();
}

};

output_factory_t<output_asio_instance> foo2;

Re: Trying to compile ASIO output plugin and...

Reply #3
Here
Code: [Select]
void instantiate(service_ptr_t<output> & p_out, const GUID & p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth) {
    p_out = new service_impl_t<output_asio_instance>(p_device, p_buffer_length, p_dither, p_bitdepth);
};
you need to return instance of class derived from output (or output_v2), not from output_entry_asio.

Re: Trying to compile ASIO output plugin and...

Reply #4
Hello,

I follow advices and with the code below : this compile.
However, the output entry does not appear in FB2K output combobox.
I probably miss something, but I don't know what...

(output_asio derives from output_v2).

Code: [Select]
class output_asio_instance : public output_asio
{

public:

output_asio_instance()
{
Init();
OpenDriver = false;
};

output_asio_instance(const GUID & p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth)
{
Init();
OpenDriver = false;
}

~output_asio_instance(void)
{
if (OpenDriver) ParamMsg(MSG_CLOSE_DRIVER).Call();
}

static bool g_advanced_settings_query() { return FALSE; };
static bool g_needs_dither_config() { return TRUE; };
static bool g_needs_bitdepth_config() { return TRUE; };
static bool g_needs_device_list_prefixes() { return TRUE; };

static void g_advanced_settings_popup(HWND p_parent, POINT p_menupoint) {};

static const char* g_get_name(void) { return NAME2; }
static const GUID g_get_guid(void) { return g_guid; }

static t_uint32 get_config_flags(void) {
//return ;
};

static void g_enum_devices(output_device_enum_callback & p_callback) {
p_callback = output_device_enum_callback_impl();
};

void
open(const t_samplespec & sspec) {
//const int RetCode = ParamMsg(MSG_OPEN, sspec.m_sample_rate, format_code, bps, sspec.m_channels).Call();
//OpenDriver = RetCode != 0;
//return RetCode;
};
void
pause(bool) {};
void
flush(void) {
ParamMsg(MSG_FLUSH).Call();
};
void
volume_set(double) {};
void
update(bool &) {};
void
process_samples(const audio_chunk &) {};
t_size
can_write_samples(void) {
return ParamMsg(MSG_CAN_WRITE).Call();
};

double
get_latency() {
return pPcmAsio->MsgGetLatency();
}


int
open_ex(int srate, int bps, int nch, int format_code)
{
const int RetCode = ParamMsg(MSG_OPEN, srate, format_code, bps, nch).Call();
OpenDriver = RetCode != 0;
return RetCode;
}

//int
// output_asio_instance::can_write(void)
//{
// return ParamMsg(MSG_CAN_WRITE).Call();
//}

int
write(const char* data, int bytes)
{
return ParamMsg(MSG_WRITE, bytes,
reinterpret_cast<unsigned char*>(const_cast<char*>(data))).Call();
}

//int
// output_asio_instance::get_latency_bytes(void)
//{
// return pPcmAsio->MsgGetLatency();
//}

void
pause(int state)
{
ParamMsg(MSG_PAUSE, state).Call();
}

void
force_play(void)
{
ParamMsg(MSG_PLAY).Call();
}

//int
// output_asio_instance::do_flush(void)
//{
// return ParamMsg(MSG_FLUSH).Call();
//}

int
is_playing(void)
{
return !!pPcmAsio->MsgGetLatency();
}

protected:
class output_device_enum_callback_impl : public output_device_enum_callback {

public:
output_device_enum_callback_impl() {};
~output_device_enum_callback_impl() {};

void on_device(const GUID &p_guid, const char *p_name, unsigned p_name_length) {

//p_guid = p_device;
p_name = output_asio_instance::g_get_name();
p_name_length = 20;
}
};

};


class output_entry_impl : public output_entry {

output_entry_impl();

~output_entry_impl() {};

public:

void instantiate(service_ptr_t<output> & p_out, const GUID & p_device, double p_buffer_length, bool p_dither, t_uint32 p_bitdepth) {
p_out = new service_impl_t<output_asio_instance>(p_device, p_buffer_length, p_dither, p_bitdepth);
};

void enum_devices(output_device_enum_callback &) {};
GUID get_guid() { return output_asio_instance::g_get_guid(); };
const char *get_name(void) { return output_asio_instance::g_get_name(); };
void advanced_settings_popup(HWND, POINT) {  };

t_uint32 get_config_flags(){
output_asio_instance::get_config_flags();
};

};


output_factory_t<output_asio_instance> foo2;


Re: Trying to compile ASIO output plugin and...

Reply #5
Hello,

Still trying to make it works... and still not working as expected.
Would a nice developer give me some example code to make a functional output plugin (eventually in PM) ?

Regards,
AF.

Re: Trying to compile ASIO output plugin and...

Reply #6
Hello,

Finally managed to register the output.
Trying now to glue things (foobar and asio) together.

When I launch Foobar and trying to play a song, I can see callbacks (see below).
Can somebody tell me if it's the correct sequence, and what is expected in each call ?

Questions:
- get_guid : I assume it's used to get guid of the output device
- output constructor : in case of Asio, init (and open ?) the driver
- volume_set : trivial
- get_latency : seems to be used to know the length of sample to be played
But in which buffers samples are stored ? Is there some helpers functions ?
- update : Which is the purpose ?? Also there is an on_update() function (currently commented in my impl)

Apart SDK, could somebody shows me sample code ?

Regards.

Code: [Select]
Components loaded in: 0:01.450880
Configuration read in: 0:00.002234
User Interface initialized in: 0:00.069661
Startup time : 0:01.548173
output_asio_impl::get_guid
output_asio_impl::constructor
output_asio_impl::volume_set
output_asio_impl::volume_set
output_asio_impl::pause
output_asio_impl::update
output_asio_impl::get_latency
output_asio_impl::update
output_asio_impl::get_latency
output_asio_impl::update

Re: Trying to compile ASIO output plugin and...

Reply #7
- get_guid : I assume it's used to get guid of the output device
In g_get_guid() you return the GUID of your component. Use the Create GUID menu command in Visual Studio to generate your own.
Individual ASIO devices you would handle in g_enum_devices(). The callback has function on_device() which you call with the GUID and the name of each ASIO device.

- output constructor : in case of Asio, init (and open ?) the driver
Yes. The constructor will only be called when playback starts with your output.

- get_latency : seems to be used to know the length of sample to be played
But in which buffers samples are stored ? Is there some helpers functions ?
You return how many seconds the playback is delayed. This includes the amount of data you have buffered and the latency of the output, if you know it.
You allocate the buffer. Your constructor is told how many seconds of buffer user has requested so use that to calculate how many bytes you need.

- update : Which is the purpose ?? Also there is an on_update() function (currently commented in my impl)
You tell the core if you have space in your buffers to take more bytes. Once you have room in buffers and you inform the core, process_samples() will get called with new data.

Apart SDK, could somebody shows me sample code ?
There's saivert's pipe output. Though it doesn't do everything and doesn't calculate accurate latencies.

 
SimplePortal 1.0.0 RC1 © 2008-2018