blob: 961a88b962895a7ef5021c529edf604f4c231f59 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Pierre Souchay , Jean Le Feuvre
* Copyright (c) Telecom ParisTech 2008-2019
* All rights reserved
*
* Jack audio output module : output audio thru the jackd daemon
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <jack/types.h>
#include <jack/jack.h>
#include <jack/ringbuffer.h>
#include <gpac/modules/audio_out.h>
#define MAX_JACK_CLIENT_NAME_SZ 128
/*
* This structure defines the handle to a Jack driver
*/
typedef struct
{
char jackClientName[MAX_JACK_CLIENT_NAME_SZ];
jack_client_t *jack;
jack_port_t **jackPorts;
jack_nframes_t currentBlockSize;
u32 numChannels;
char *buffer;
u32 bufferSz;
u32 bytesPerSample;
Bool isActive;
Bool autoConnect;
jack_default_audio_sample_t **channels;
float volume;
} JackContext;
static void
Jack_cleanup (JackContext * ctx)
{
u32 channels;
if (ctx == NULL)
return;
GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO, ("[Jack] Jack_cleanup\n"));
if (ctx->jack != NULL && ctx->isActive)
{
jack_deactivate (ctx->jack);
}
if (ctx->buffer != NULL)
{
gf_free(ctx->buffer);
ctx->bufferSz = 0;
ctx->buffer = NULL;
}
if (ctx->jackPorts != NULL)
{
for (channels = 0; channels < ctx->numChannels; channels++)
{
if (ctx->jackPorts[channels] != NULL)
jack_port_unregister (ctx->jack, ctx->jackPorts[channels]);
ctx->jackPorts[channels] = NULL;
}
gf_free(ctx->jackPorts);
ctx->jackPorts = NULL;
}
if (ctx->jack != NULL)
{
jack_client_close (ctx->jack);
}
if (ctx->channels != NULL)
{
gf_free(ctx->channels);
ctx->channels = NULL;
}
ctx->numChannels = 0;
ctx->currentBlockSize = 0;
memset (ctx->jackClientName, 0, MAX_JACK_CLIENT_NAME_SZ);
ctx->jack = NULL;
ctx->isActive = 0;
}
/**
* The callback called by the jack thread
*/
static int
process_callback (jack_nframes_t nframes, void *arg)
{
unsigned int channel, i;
size_t toRead;
size_t bytesToRead;
GF_AudioOutput *dr = (GF_AudioOutput *) arg;
JackContext *ctx;
if (dr == NULL)
{
// Should not happen
return 1;
}
ctx = dr->opaque;
toRead = nframes * ctx->numChannels;
bytesToRead = toRead * ctx->bytesPerSample;
dr->FillBuffer (dr->audio_renderer, (void *) ctx->buffer,
bytesToRead);
if (ctx->bytesPerSample == 2)
{
short *tmpBuffer;
tmpBuffer = (short *) ctx->buffer;
for (channel = 0; channel < nframes; channel += ctx->numChannels)
{
for (i = 0; i < ctx->numChannels; i++)
ctx->channels[i][channel] =
(float) (ctx->volume / 32768.0 *
(tmpBuffer[channel * ctx->numChannels + i]));
}
}
else
{
for (channel = 0; channel < nframes; channel += ctx->numChannels)
{
for (i = 0; i < ctx->numChannels; i++)
ctx->channels[i][channel] =
(float) (ctx->volume / 255.0 *
(ctx->buffer[channel * ctx->numChannels + i]));
}
}
return 0;
}
/**
* Called when jackbuffer size change
*/
static int
onBufferSizeChanged (jack_nframes_t nframes, void *arg)
{
GF_AudioOutput *dr = (GF_AudioOutput *) arg;
JackContext *ctx;
size_t realBuffSize;
u32 channel;
if (dr == NULL)
{
// Should not happen
return 1;
}
ctx = dr->opaque;
realBuffSize = nframes * ctx->numChannels * sizeof (short);
if (ctx->buffer != NULL && ctx->bufferSz == realBuffSize)
return 0;
if (ctx->channels != NULL)
gf_free(ctx->channels);
ctx->channels = NULL;
ctx->channels = gf_calloc (ctx->numChannels, sizeof (jack_default_audio_sample_t *));
if (ctx->channels == NULL)
{
Jack_cleanup (ctx);
return 2;
}
for (channel = 0; channel < ctx->numChannels; channel++)
{
ctx->channels[channel] =
jack_port_get_buffer (ctx->jackPorts[channel], nframes);
if (ctx->channels[channel] == NULL)
{
Jack_cleanup (ctx);
return 3;
}
}
if (ctx->buffer != NULL)
gf_free(ctx->buffer);
ctx->buffer = gf_calloc (realBuffSize, sizeof (char));
if (ctx->buffer == NULL)
{
Jack_cleanup (ctx);
return 4;
}
ctx->bufferSz = realBuffSize;
GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
("[Jack] onBufferSizeChanged : resized to %d.\n", realBuffSize));
if (ctx->buffer == NULL)
{
ctx->bufferSz = 0;
Jack_cleanup (ctx);
return 5;
}
return 0;
}
static GF_Err
Jack_Setup (GF_AudioOutput * dr, void *os_handle, u32 num_buffers,
u32 total_duration)
{
JackContext *ctx = (JackContext *) dr->opaque;
jack_status_t status;
jack_options_t options = JackNullOption;
memset (ctx->jackClientName, 0, MAX_JACK_CLIENT_NAME_SZ);
snprintf (ctx->jackClientName, MAX_JACK_CLIENT_NAME_SZ, "gpac-%u", gf_sys_get_process_id() );
ctx->autoConnect = gf_module_get_bool((GF_BaseInterface *)dr, "auto-connect");
if (!gf_module_get_bool((GF_BaseInterface *)dr, "start-server"))
options |= JackNoStartServer;
ctx->jack = jack_client_open (ctx->jackClientName, options, &status, NULL);
if (status & JackNameNotUnique)
{
GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
("[Jack] Cannot open connection to jackd as %s since name was not unique.\n",
ctx->jackClientName));
Jack_cleanup (ctx);
return GF_IO_ERR;
}
if (ctx->jack == NULL)
{
GF_LOG (GF_LOG_ERROR, GF_LOG_MMIO,
("[Jack] Cannot open connection to jackd as %s.\n",
ctx->jackClientName));
return GF_IO_ERR;
}
return GF_OK;
}
static void
Jack_Shutdown (GF_AudioOutput * dr)
{
JackContext *ctx = (JackContext *) dr->opaque;
Jack_cleanup (ctx);
}
#define JACK_PORT_NAME_MAX_SZ 128
static GF_Err
Jack_Configure(GF_AudioOutput * dr, u32 * SampleRate, u32 * NbChannels, u32 *audioFormat, u64 channel_cfg)
{
u32 channels;
u32 i;
char port_name[JACK_PORT_NAME_MAX_SZ];
JackContext *ctx = (JackContext *) dr->opaque;
if (!ctx)
return GF_BAD_PARAM;
//only support for PCM 8/16/24/32 packet mode
switch (*audioFormat) {
case GF_AUDIO_FMT_U8:
ctx->bytesPerSample = 1;
break;
default:
//otherwise force PCM16
*audioFormat = GF_AUDIO_FMT_S16;
case GF_AUDIO_FMT_S16:
ctx->bytesPerSample = 2;
break;
}
ctx->numChannels = *NbChannels;
*SampleRate = jack_get_sample_rate (ctx->jack);
GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
("[Jack] Jack_ConfigureOutput channels=%d, srate=%d audio format %s\n",
*NbChannels, *SampleRate, gf_audio_fmt_name(*audioFormat) ));
if (ctx->jackPorts == NULL)
ctx->jackPorts = gf_calloc (ctx->numChannels, sizeof (jack_port_t *));
if (ctx->jackPorts == NULL)
{
goto exit_cleanup;
}
if (!ctx->isActive)
{
for (channels = 0; channels < ctx->numChannels; channels++)
{
snprintf (port_name, JACK_PORT_NAME_MAX_SZ, "playback_%d",
channels + 1);
ctx->jackPorts[channels] =
jack_port_register (ctx->jack, port_name, JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if (ctx->jackPorts[channels] == NULL)
goto exit_cleanup;
}
// onBufferSizeChanged (jack_get_buffer_size (ctx->jack), dr);
// if (!ctx->jack) return GF_IO_ERR;
jack_set_buffer_size_callback (ctx->jack, onBufferSizeChanged, dr);
jack_set_process_callback (ctx->jack, process_callback, dr);
}
ctx->currentBlockSize = jack_get_buffer_size (ctx->jack);
if (!ctx->isActive)
{
jack_activate (ctx->jack);
if (ctx->autoConnect)
{
const char **matching_outputs =
jack_get_ports (ctx->jack, NULL, NULL,
JackPortIsInput | JackPortIsPhysical |
JackPortIsTerminal);
if (matching_outputs != NULL)
{
channels = 0;
i = 0;
while (matching_outputs[i] != NULL
&& channels < ctx->numChannels)
{
if (!jack_connect (ctx->jack,
jack_port_name (ctx->
jackPorts[channels++]),
matching_outputs[i]))
{
GF_LOG (GF_LOG_INFO, GF_LOG_MMIO,
("[Jack] Jack_ConfigureOutput: Failed to connect port[%d] to %s.\n",
channels - 1, matching_outputs[i]));
}
i++;
}
}
jack_free (matching_outputs);
}
ctx->isActive = GF_TRUE;
}
return GF_OK;
exit_cleanup:
Jack_cleanup (ctx);
return GF_IO_ERR;
}
static void
Jack_SetVolume (GF_AudioOutput * dr, u32 Volume)
{
JackContext *ctx = (JackContext *) dr->opaque;
if (ctx == NULL)
{
return;
}
/* We support ajust the volume to more than 100, up to +6dB even
* if frontends don't support it, it may be useful */
if (Volume > 400)
Volume = 400;
ctx->volume = (float) Volume / 100.0;
GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
("[Jack] Jack_SetVolume: Volume set to %d%%.\n", Volume));
}
static void
Jack_SetPan (GF_AudioOutput * dr, u32 Pan)
{
GF_LOG (GF_LOG_INFO, GF_LOG_MMIO, ("[Jack] Jack_SetPan: Not supported.\n"));
}
static void
Jack_SetPriority (GF_AudioOutput * dr, u32 Priority)
{
/**
* Jack manages the priority itself, we don't need
* to interfere here...
*/
}
static u32
Jack_GetAudioDelay (GF_AudioOutput * dr)
{
jack_nframes_t max = 0;
jack_nframes_t latency;
u32 channel;
JackContext *ctx = (JackContext *) dr->opaque;
if (ctx == NULL)
{
return 0;
}
jack_recompute_total_latencies (ctx->jack);
for (channel = 0; channel < ctx->numChannels; channel++)
{
latency =
jack_port_get_total_latency (ctx->jack, ctx->jackPorts[channel]);
if (latency > max)
max = latency;
}
channel = max * 1000 / jack_get_sample_rate (ctx->jack);
GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
("[Jack] Jack_GetAudioDelay latency = %d ms.\n", channel));
return channel;
}
static GF_Err
Jack_QueryOutputSampleRate (GF_AudioOutput * dr, u32 * desired_sr,
u32 * NbChannels, u32 * nbBitsPerSample)
{
JackContext *ctx = (JackContext *) dr->opaque;
if (!ctx)
return GF_IO_ERR;
*desired_sr = jack_get_sample_rate (ctx->jack);
*NbChannels = 2;
GF_LOG (GF_LOG_DEBUG, GF_LOG_MMIO,
("[Jack] Jack output sample rate %d\n", *desired_sr));
return GF_OK;
}
static GF_GPACArg JackArgs[] = {
GF_DEF_ARG("auto-connect", NULL, "automatically connect to jackd", "true", NULL, GF_ARG_BOOL, 0),
GF_DEF_ARG("start-server", NULL, "automatically start JACK server if not running", "true", NULL, GF_ARG_BOOL, 0),
{0},
};
void *
NewJackOutput ()
{
JackContext *ctx;
GF_AudioOutput *driv;
GF_SAFEALLOC (ctx, JackContext);
if (!ctx)
return NULL;
GF_SAFEALLOC (driv, GF_AudioOutput);
if (!driv)
{
gf_free(ctx);
return NULL;
}
driv->opaque = ctx;
driv->SelfThreaded = 1;
driv->Setup = Jack_Setup;
driv->Shutdown = Jack_Shutdown;
driv->Configure = Jack_Configure;
driv->GetAudioDelay = Jack_GetAudioDelay;
driv->SetVolume = Jack_SetVolume;
driv->SetPan = Jack_SetPan;
driv->SetPriority = Jack_SetPriority;
driv->QueryOutputSampleRate = Jack_QueryOutputSampleRate;
driv->args = JackArgs;
driv->description = "Audio output using JACK";
ctx->jack = NULL;
ctx->numChannels = 0;
ctx->jackPorts = NULL;
ctx->currentBlockSize = 0;
ctx->numChannels = 0;
ctx->buffer = NULL;
ctx->bufferSz = 0;
ctx->bytesPerSample = 0;
ctx->isActive = GF_FALSE;
ctx->autoConnect = GF_FALSE;
ctx->volume = 1.0;
GF_REGISTER_MODULE_INTERFACE (driv, GF_AUDIO_OUTPUT_INTERFACE, "jack", "gpac distribution");
return driv;
}
void
DeleteJackOutput (void *ifce)
{
GF_AudioOutput *dr = (GF_AudioOutput *) ifce;
JackContext *ctx = (JackContext *) dr->opaque;
Jack_cleanup (ctx);
gf_free(ctx);
dr->opaque = NULL;
gf_free(dr);
}
/*
* ********************************************************************
* interface
*/
GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
static u32 si [] = {
GF_AUDIO_OUTPUT_INTERFACE,
0
};
return si;
}
GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface (u32 InterfaceType)
{
if (InterfaceType == GF_AUDIO_OUTPUT_INTERFACE)
{
return NewJackOutput ();
}
return NULL;
}
GPAC_MODULE_EXPORT
void ShutdownInterface (GF_BaseInterface * ifce)
{
if (ifce->InterfaceType == GF_AUDIO_OUTPUT_INTERFACE)
DeleteJackOutput ((GF_AudioOutput *) ifce);
}
GPAC_MODULE_STATIC_DECLARATION( jack )