/* * This file is part of Moonlight Embedded. * * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Moonlight is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Moonlight; if not, see . */ #include "audio.h" #include #include #include "bcm_host.h" #include "ilclient.h" static OpusMSDecoder* decoder; ILCLIENT_T* handle; COMPONENT_T* component; static OMX_BUFFERHEADERTYPE *buf; static short pcmBuffer[FRAME_SIZE * MAX_CHANNEL_COUNT]; static int channelCount; static int omx_renderer_init(int audioConfiguration, POPUS_MULTISTREAM_CONFIGURATION opusConfig, void* context, int arFlags) { int rc, error; OMX_ERRORTYPE err; unsigned char omxMapping[MAX_CHANNEL_COUNT]; char* componentName = "audio_render"; channelCount = opusConfig->channelCount; /* The supplied mapping array has order: FL-FR-C-LFE-RL-RR * OMX expects the order: FL-FR-LFE-C-RL-RR * We need copy the mapping locally and swap the channels around. */ memcpy(omxMapping, opusConfig->mapping, sizeof(omxMapping)); if (opusConfig->channelCount > 2) { omxMapping[2] = opusConfig->mapping[3]; omxMapping[3] = opusConfig->mapping[2]; } decoder = opus_multistream_decoder_create(opusConfig->sampleRate, opusConfig->channelCount, opusConfig->streams, opusConfig->coupledStreams, omxMapping, &rc); handle = ilclient_init(); if (handle == NULL) { fprintf(stderr, "IL client init failed\n"); return -1; } if (ilclient_create_component(handle, &component, componentName, ILCLIENT_DISABLE_ALL_PORTS | ILCLIENT_ENABLE_INPUT_BUFFERS) != 0) { fprintf(stderr, "Component create failed\n"); return -1; } if (ilclient_change_component_state(component, OMX_StateIdle)!= 0) { fprintf(stderr, "Couldn't change state to Idle\n"); return -1; } // must be before we enable buffers OMX_AUDIO_PARAM_PORTFORMATTYPE audioPortFormat; memset(&audioPortFormat, 0, sizeof(OMX_AUDIO_PARAM_PORTFORMATTYPE)); audioPortFormat.nSize = sizeof(OMX_AUDIO_PARAM_PORTFORMATTYPE); audioPortFormat.nVersion.nVersion = OMX_VERSION; audioPortFormat.nPortIndex = 100; OMX_GetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPortFormat, &audioPortFormat); audioPortFormat.eEncoding = OMX_AUDIO_CodingPCM; OMX_SetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPortFormat, &audioPortFormat); OMX_AUDIO_PARAM_PCMMODETYPE sPCMMode; memset(&sPCMMode, 0, sizeof(OMX_AUDIO_PARAM_PCMMODETYPE)); sPCMMode.nSize = sizeof(OMX_AUDIO_PARAM_PCMMODETYPE); sPCMMode.nVersion.nVersion = OMX_VERSION; sPCMMode.nPortIndex = 100; sPCMMode.nChannels = channelCount; sPCMMode.eNumData = OMX_NumericalDataSigned; sPCMMode.eEndian = OMX_EndianLittle; sPCMMode.nSamplingRate = opusConfig->sampleRate; sPCMMode.bInterleaved = OMX_TRUE; sPCMMode.nBitPerSample = 16; sPCMMode.ePCMMode = OMX_AUDIO_PCMModeLinear; switch(channelCount) { case 1: sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelCF; break; case 8: sPCMMode.eChannelMapping[7] = OMX_AUDIO_ChannelRS; case 7: sPCMMode.eChannelMapping[6] = OMX_AUDIO_ChannelLS; case 6: sPCMMode.eChannelMapping[5] = OMX_AUDIO_ChannelRR; case 5: sPCMMode.eChannelMapping[4] = OMX_AUDIO_ChannelLR; case 4: sPCMMode.eChannelMapping[3] = OMX_AUDIO_ChannelLFE; case 3: sPCMMode.eChannelMapping[2] = OMX_AUDIO_ChannelCF; case 2: sPCMMode.eChannelMapping[1] = OMX_AUDIO_ChannelRF; sPCMMode.eChannelMapping[0] = OMX_AUDIO_ChannelLF; break; } err = OMX_SetParameter(ilclient_get_handle(component), OMX_IndexParamAudioPcm, &sPCMMode); if(err != OMX_ErrorNone){ fprintf(stderr, "PCM mode unsupported\n"); return -1; } OMX_CONFIG_BRCMAUDIODESTINATIONTYPE arDest; char* audio_device = (char*) context; if (audio_device == NULL) audio_device = "hdmi"; if (audio_device && strlen(audio_device) < sizeof(arDest.sName)) { memset(&arDest, 0, sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE)); arDest.nSize = sizeof(OMX_CONFIG_BRCMAUDIODESTINATIONTYPE); arDest.nVersion.nVersion = OMX_VERSION; strcpy((char *)arDest.sName, audio_device); err = OMX_SetParameter(ilclient_get_handle(component), OMX_IndexConfigBrcmAudioDestination, &arDest); if (err != OMX_ErrorNone) { fprintf(stderr, "Error on setting audio destination\nomx option must be set to hdmi or local\n"); return -1; } } // input port ilclient_enable_port_buffers(component, 100, NULL, NULL, NULL); ilclient_enable_port(component, 100); err = ilclient_change_component_state(component, OMX_StateExecuting); if (err < 0) { fprintf(stderr, "Couldn't change state to Executing\n"); return -1; } return 0; } static void omx_renderer_cleanup() { if (decoder != NULL) opus_multistream_decoder_destroy(decoder); if (handle != NULL) { if((buf = ilclient_get_input_buffer(component, 100, 1)) == NULL){ fprintf(stderr, "Can't get audio buffer\n"); exit(EXIT_FAILURE); } buf->nFilledLen = 0; buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN | OMX_BUFFERFLAG_EOS; if(OMX_EmptyThisBuffer(ILC_GET_HANDLE(component), buf) != OMX_ErrorNone){ fprintf(stderr, "Can't empty audio buffer\n"); return; } ilclient_disable_port_buffers(component, 100, NULL, NULL, NULL); ilclient_change_component_state(component, OMX_StateIdle); ilclient_change_component_state(component, OMX_StateLoaded); } } static void omx_renderer_decode_and_play_sample(char* data, int length) { int decodeLen = opus_multistream_decode(decoder, data, length, pcmBuffer, FRAME_SIZE, 0); if (decodeLen > 0) { buf = ilclient_get_input_buffer(component, 100, 1); buf->nOffset = 0; buf->nFlags = OMX_BUFFERFLAG_TIME_UNKNOWN; int bufLength = decodeLen * sizeof(short) * channelCount; memcpy(buf->pBuffer, pcmBuffer, bufLength); buf->nFilledLen = bufLength; int r = OMX_EmptyThisBuffer(ilclient_get_handle(component), buf); if (r != OMX_ErrorNone) { fprintf(stderr, "Empty buffer error\n"); } } else { printf("Opus error from decode: %d\n", decodeLen); } } AUDIO_RENDERER_CALLBACKS audio_callbacks_omx = { .init = omx_renderer_init, .cleanup = omx_renderer_cleanup, .decodeAndPlaySample = omx_renderer_decode_and_play_sample, .capabilities = CAPABILITY_DIRECT_SUBMIT, };