flycast/core/audio/audiobackend_omx.cpp

338 lines
9.5 KiB
C++

#if USE_OMX
#include "audiostream.h"
#include "cfg/option.h"
#include <IL/OMX_Broadcom.h>
#include <unistd.h>
#define PORT_INDEX 100
#define OUTPUT_FREQ 44100
class OMXAudioBackend : public AudioBackend
{
OMX_HANDLETYPE omx_handle = nullptr;
OMX_STATETYPE omx_state = OMX_StateInvalid;
size_t audio_buffer_idx = 0;
OMX_BUFFERHEADERTYPE** audio_buffers = nullptr;
u32 latency_max = 0;
u32 buffer_count = 0;
u32 buffer_size = 0;
u32 buffer_length = 0;
pthread_mutex_t audio_lock;
pthread_cond_t omx_state_cond;
static OMX_ERRORTYPE eventHandler(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_EVENTTYPE eEvent,
OMX_IN OMX_U32 nData1,
OMX_IN OMX_U32 nData2,
OMX_IN OMX_PTR pEventData)
{
OMXAudioBackend *backend = (OMXAudioBackend *)pAddData;
pthread_mutex_lock(&backend->audio_lock);
if(eEvent == OMX_EventCmdComplete && nData1 == OMX_CommandStateSet)
{
backend->omx_state = (OMX_STATETYPE)nData2;
pthread_cond_signal(&backend->omx_state_cond);
}
pthread_mutex_unlock(&backend->audio_lock);
return OMX_ErrorNone;
}
static OMX_ERRORTYPE emptyBufferDone(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer)
{
return OMX_ErrorNone;
}
static OMX_ERRORTYPE fillBufferDone(
OMX_OUT OMX_HANDLETYPE hComponent,
OMX_OUT OMX_PTR pAppData,
OMX_OUT OMX_BUFFERHEADERTYPE* pBuffer)
{
return OMX_ErrorNone;
}
void waitForState(OMX_STATETYPE state)
{
pthread_mutex_lock(&audio_lock);
while(omx_state != state)
pthread_cond_wait(&omx_state_cond, &audio_lock);
pthread_mutex_unlock(&audio_lock);
}
u32 getLatency()
{
OMX_PARAM_U32TYPE param;
memset(&param, 0, sizeof(OMX_PARAM_U32TYPE));
param.nSize = sizeof(OMX_PARAM_U32TYPE);
param.nVersion.nVersion = OMX_VERSION;
param.nPortIndex = PORT_INDEX;
OMX_ERRORTYPE error = OMX_GetConfig(omx_handle, OMX_IndexConfigAudioRenderingLatency, &param);
if(error != OMX_ErrorNone)
WARN_LOG(AUDIO, "OMX: failed to get OMX configuration (OMX_IndexConfigAudioRenderingLatency). Error 0x%X", error);
return param.nU32 * 1000 / OUTPUT_FREQ;
}
public:
OMXAudioBackend()
: AudioBackend("omx", "OpenMAX IL") {}
bool init() override
{
OMX_ERRORTYPE error;
error = OMX_Init();
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: OMX_Init() failed. Error 0x%X", error);
return false;
}
// Initialize settings
latency_max = config::OmxAudioLatency;
buffer_size = config::AudioBufferSize * 4;
buffer_count = 2 + OUTPUT_FREQ * latency_max / (buffer_size * 1000);
OMX_CALLBACKTYPE callbacks;
callbacks.EventHandler = eventHandler;
callbacks.EmptyBufferDone = emptyBufferDone;
callbacks.FillBufferDone = fillBufferDone;
error = OMX_GetHandle(&omx_handle, (OMX_STRING)"OMX.broadcom.audio_render", this, &callbacks);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: OMX_GetHandle() failed. Error 0x%X", error);
OMX_Deinit();
return false;
}
OMX_PARAM_PORTDEFINITIONTYPE param;
memset(&param, 0, sizeof(OMX_PARAM_PORTDEFINITIONTYPE));
param.nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
param.nVersion.nVersion = OMX_VERSION;
param.nPortIndex = PORT_INDEX;
param.nBufferSize = buffer_size;
param.nBufferCountActual = buffer_count;
param.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
error = OMX_SetParameter(omx_handle, OMX_IndexParamPortDefinition, &param);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to set OMX_IndexParamPortDefinition. Error 0x%X", error);
OMX_Deinit();
return false;
}
OMX_AUDIO_PARAM_PCMMODETYPE pcm;
memset(&pcm, 0, sizeof(OMX_AUDIO_PARAM_PCMMODETYPE));
pcm.nSize = sizeof(OMX_AUDIO_PARAM_PCMMODETYPE);
pcm.nVersion.nVersion = OMX_VERSION;
pcm.nPortIndex = PORT_INDEX;
pcm.nChannels = 2;
pcm.eNumData = OMX_NumericalDataSigned;
pcm.eEndian = OMX_EndianLittle;
pcm.nSamplingRate = OUTPUT_FREQ;
pcm.bInterleaved = OMX_TRUE;
pcm.nBitPerSample = 16;
pcm.ePCMMode = OMX_AUDIO_PCMModeLinear;
pcm.eChannelMapping[1] = OMX_AUDIO_ChannelRF;
pcm.eChannelMapping[0] = OMX_AUDIO_ChannelLF;
error = OMX_SetParameter(omx_handle, OMX_IndexParamAudioPcm, &pcm);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to set OMX_IndexParamAudioPcm. Error 0x%X", error);
OMX_Deinit();
return false;
}
// Disable all ports
error = OMX_SendCommand(omx_handle, OMX_CommandPortDisable, PORT_INDEX, NULL);
if (error != OMX_ErrorNone)
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortDisable. Error 0x%X", error);
OMX_PORT_PARAM_TYPE param2;
memset(&param2, 0, sizeof(OMX_PORT_PARAM_TYPE));
param2.nSize = sizeof(OMX_PORT_PARAM_TYPE);
param2.nVersion.nVersion = OMX_VERSION;
error = OMX_GetParameter(omx_handle, OMX_IndexParamOtherInit, &param2);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to get OMX_IndexParamOtherInit. Error 0x%X", error);
}
else
{
for(u32 i = 0; i < param2.nPorts; i++)
{
u32 port = param2.nStartPortNumber + i;
error = OMX_SendCommand(omx_handle, OMX_CommandPortDisable, port, NULL);
if(error != OMX_ErrorNone)
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortDisable on port %u. Error 0x%X", port, error);
}
}
// Go into idle state
error = OMX_SendCommand(omx_handle, OMX_CommandStateSet, OMX_StateIdle, NULL);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to set OMX_CommandStateSet. Error 0x%X", error);
OMX_Deinit();
return false;
}
waitForState(OMX_StateIdle);
// Check if we're in a state able to recieve buffers
OMX_STATETYPE state;
error = OMX_GetState(omx_handle, &state);
if (error != OMX_ErrorNone || !(state == OMX_StateIdle || state == OMX_StateExecuting || state == OMX_StatePause))
{
WARN_LOG(AUDIO, "OMX: state is incorrect. State 0x%X; Error 0x%X", state, error);
OMX_Deinit();
return false;
}
// Create audio buffers
INFO_LOG(AUDIO, "OMX: creating %u buffers", buffer_count);
// Enable port
error = OMX_SendCommand(omx_handle, OMX_CommandPortEnable, PORT_INDEX, NULL);
if (error != OMX_ErrorNone)
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandPortEnable. Error 0x%X", error);
// Free audio buffers if they're allocated
if (audio_buffers != NULL)
delete[] audio_buffers;
// Allocate buffers
audio_buffers = new OMX_BUFFERHEADERTYPE*[buffer_count];
for (size_t i = 0; i < buffer_count; i++)
{
error = OMX_AllocateBuffer(omx_handle, &audio_buffers[i], PORT_INDEX, NULL, buffer_size);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to allocate buffer[%u]. Error 0x%X", i, error);
OMX_Deinit();
return false;
}
}
// Set state to executing
error = OMX_SendCommand(omx_handle, OMX_CommandStateSet, OMX_StateExecuting, NULL);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to do OMX_CommandStateSet. Error 0x%X", error);
OMX_Deinit();
return false;
}
waitForState(OMX_StateExecuting);
// Empty buffers
for (size_t i = 0; i < buffer_count; i++)
{
memset(audio_buffers[i]->pBuffer, 0, buffer_size);
audio_buffers[i]->nOffset = 0;
audio_buffers[i]->nFilledLen = buffer_size;
error = OMX_EmptyThisBuffer(omx_handle, audio_buffers[i]);
if(error != OMX_ErrorNone)
WARN_LOG(AUDIO, "OMX: failed to empty buffer[%u]. Error 0x%X", i, error);
}
const char* output_device = "local";
if (config::OmxAudioHdmi)
output_device = (const char*)"hdmi";
// Set audio destination
OMX_CONFIG_BRCMAUDIODESTINATIONTYPE ar_dest;
memset(&ar_dest, 0, sizeof(ar_dest));
ar_dest.nSize = sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE);
ar_dest.nVersion.nVersion = OMX_VERSION;
strcpy((char *)ar_dest.sName, output_device);
error = OMX_SetConfig(omx_handle, OMX_IndexConfigBrcmAudioDestination, &ar_dest);
if (error != OMX_ErrorNone)
{
WARN_LOG(AUDIO, "OMX: failed to set OMX configuration (OMX_IndexConfigBrcmAudioDestination). Error 0x%X", error);
OMX_Deinit();
return false;
}
audio_buffer_idx = 0;
buffer_length = 0;
INFO_LOG(AUDIO, "OMX: audio output to '%s'", ar_dest.sName);
return true;
}
u32 push(const void* frame, u32 samples, bool wait) override
{
if(audio_buffers == NULL)
return 1;
size_t data_size = samples * 4;
while(data_size > 0)
{
size_t copy_size = std::min(buffer_size - buffer_length, data_size);
// Don't have more than maximum audio latency
u32 latency = getLatency();
if(latency > latency_max)
{
usleep((latency - latency_max) * 1000);
}
else if(latency == 0)
{
INFO_LOG(AUDIO, "OMX: underrun occurred");
}
memcpy(audio_buffers[audio_buffer_idx]->pBuffer + buffer_length, frame, copy_size);
buffer_length += copy_size;
frame = (char *)frame + copy_size;
// Flush buffer and swap
if(buffer_length >= buffer_size)
{
audio_buffers[audio_buffer_idx]->nOffset = 0;
audio_buffers[audio_buffer_idx]->nFilledLen = buffer_size;
OMX_ERRORTYPE error = OMX_EmptyThisBuffer(omx_handle, audio_buffers[audio_buffer_idx]);
if(error != OMX_ErrorNone)
INFO_LOG(AUDIO, "OMX: failed to empty buffer[%u]. Error 0x%X", audio_buffer_idx, error);
audio_buffer_idx = (audio_buffer_idx + 1) % buffer_count;
buffer_length = 0;
}
data_size -= copy_size;
}
return 1;
}
void term() override
{
OMX_ERRORTYPE error;
// Is there anything else that needs to be done for omx?
error = OMX_Deinit();
if(error != OMX_ErrorNone)
WARN_LOG(AUDIO, "OMX: OMX_Deinit() failed. Error 0x%X", error);
if(audio_buffers != NULL)
{
delete[] audio_buffers;
audio_buffers = NULL;
}
}
};
static OMXAudioBackend omxAudioBackend;
#endif