Commit 1a8bd313 authored by Jean-Paul Saman's avatar Jean-Paul Saman

audio_output/amem.c: shared memory Audio output

parent 9f777cc6
......@@ -9,5 +9,7 @@ SOURCES_portaudio = portaudio.c
SOURCES_auhal = auhal.c
SOURCES_jack = jack.c
SOURCES_pulse = pulse.c
SOURCES_amem = amem.c
libvlc_LTLIBRARIES += libaout_file_plugin.la
libvlc_LTLIBRARIES += libaout_file_plugin.la \
libamem_plugin.la
/*****************************************************************************
* amem.c: memory audio driver for vlc
*****************************************************************************
* Copyright (C) 2010 M2X BV
* $Id$
*
* Authors: Jean-Paul Saman <jpsaman@videolan.org>
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_codecs.h>
#include <vlc_es.h>
#include <vlc_aout.h>
#include <vlc_playlist.h> /* changing audio delay */
#if defined(_WIN32_WINNT) || defined(WIN32)
# include <windows.h>
# include <conio.h>
# include <tchar.h>
# define NAME_MAX 255
#else
# include <limits.h>
# include <errno.h>
# include <sys/types.h>
# include <sys/mman.h>
# include <sys/stat.h> /* For mode constants */
# include <fcntl.h> /* For O_* constants */
# include <semaphore.h>
#endif
#include <assert.h>
/*****************************************************************************
* Local prototypes
*****************************************************************************/
#define FRAME_SIZE 2048
/* Audio channels */
#define CHANNELS_MAX 6
static const int pi_channels_maps[CHANNELS_MAX+1] =
{
0,
AOUT_CHAN_CENTER,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
AOUT_CHAN_CENTER | AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_REARLEFT
| AOUT_CHAN_REARRIGHT,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT,
AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
| AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT | AOUT_CHAN_LFE
};
/* Audio format */
static const char *const format_list[] = { "u8", "s8", "u16", "s16", "u16_le",
"s16_le", "u16_be", "s16_be", "fixed32",
"float32", "spdif" };
static const int format_int[] = { VLC_CODEC_U8,
VLC_CODEC_S8,
VLC_CODEC_U16N, VLC_CODEC_S16N,
VLC_CODEC_U16L,
VLC_CODEC_S16L,
VLC_CODEC_U16B,
VLC_CODEC_S16B,
VLC_CODEC_FI32,
VLC_CODEC_FL32,
VLC_CODEC_SPDIFL };
/* Shared memory header structure */
typedef struct
{
unsigned int i_nb_samples; /* number of samples in buffer */
void *samples; /* samples buffer */
} amem_buffer_t;
typedef struct
{
vlc_sem_t sem; /* handle to semaphore */
#if defined(_WIN32_WINNT) || defined(WIN32)
WAVEFORMATEX wave;
#else
vlc_fourcc_t i_format;
unsigned int i_rate;
unsigned int i_bytes_per_frame;
unsigned int i_frame_length;
unsigned i_bitspersample;
unsigned i_blockalign;
unsigned i_channels;
#endif
unsigned int i_max_samples; /* maximum samples per buffer */
unsigned int i_read_samples; /* total samples read */
unsigned int i_write_samples; /* total samples written */
/* if i_buf_write == i_buffers, then it will wrap around */
unsigned int i_read; /* buffer being read */
unsigned int i_write; /* buffer being written */
unsigned int i_buffers; /* number of sample buffers used */
} amem_shared_t;
/* Private structure */
struct aout_sys_t
{
#if defined(_WIN32_WINNT) || defined(WIN32)
HANDLE handle;
#else
int shm_fd;
#endif
amem_shared_t *header;
char *psz_name;
bool b_own_semaphore;
};
static int Open (vlc_object_t *);
static void Close(vlc_object_t *);
static void Play (aout_instance_t *);
static int SetupSharedMem(aout_instance_t *, unsigned int);
static void ExitSharedMem(aout_instance_t *);
/*
* Shared Memory helpers
*/
static void amem_sem_init(vlc_sem_t *sem, unsigned value)
{
#if defined(_WIN32_WINNT) || defined(WIN32)
sem = (vlc_sem_t *) CreateSemaphore(NULL /*LPSECURITY_ATTRIBUTES*/, 1, value, NULL);
if (sem == NULL)
abort();
#else
if (unlikely(sem_init(sem, 1 /* share between process*/, value)) == -1)
abort();
#endif
}
static int amem_sem_trywait(vlc_sem_t *sem)
{
int again = 0;
do
{
#if defined(_WIN32_WINNT) || defined(WIN32)
DWORD wait = WaitForSingleObject((HANDLE) *sem, 0L);
switch(wait)
{
case WAIT_OBJECT_0:
return VLC_SUCCESS;
case WAIT_TIMEOUT:
again++;
break;
default:
return VLC_EGENERIC;
}
#else
if (sem_trywait(sem) == 0)
break;
if (errno == EAGAIN)
again++;
#endif
if (again == 3)
return VLC_EGENERIC;
} while (again < 3);
return VLC_SUCCESS;
}
static void amem_sem_wait(vlc_sem_t *sem)
{
#if defined(_WIN32_WINNT) || defined(WIN32)
DWORD wait = WaitForSingleObject((HANDLE) *sem, INFINTE);
assert(wait == WAIT_OBJECT_0);
#else
vlc_sem_wait(sem);
#endif
}
static void amem_sem_post(vlc_sem_t *sem)
{
#if defined(_WIN32_WINNT) || defined(WIN32)
BOOL ret = ReleaseSemaphore((HANDLE) *sem, 1, NULL);
#else
int ret = vlc_sem_post(sem);
#endif
assert(ret >= 0);
}
static void amem_sem_destroy(vlc_sem_t *sem)
{
#if defined(_WIN32_WINNT) || defined(WIN32)
BOOL ret = CloseHandle((HANDLE) *sem);
assert(ret >= 0);
#else
vlc_sem_destroy(sem);
#endif
}
static amem_buffer_t *amem_buffer_get(aout_instance_t *aout, unsigned int index)
{
aout_sys_t *sys = (aout_sys_t *)aout->output.p_sys;
amem_buffer_t *buffer = NULL;
size_t buflen = sizeof(amem_buffer_t) + (aout->output.i_nb_samples * aout->output.output.i_bitspersample);
buffer = (amem_buffer_t *)((uint8_t*)sys->header + sizeof(amem_shared_t) + index * buflen);
buffer->samples = ((uint8_t *)buffer + sizeof(amem_buffer_t));
return buffer;
}
static int amem_shmem_open(aout_instance_t *aout, unsigned int i_buffers, ssize_t len, bool b_create)
{
aout_sys_t *sys = (aout_sys_t *)aout->output.p_sys;
#if defined(_WIN32_WINNT) || defined(WIN32)
TCHAR szName[NAME_MAX];
size_t nameLen = strlen(sys->psz_name);
if (nameLen > NAME_MAX)
nameLen = NAME_MAX - 1;
if (nameLen == 0)
return VLC_EGENERIC;
memcpy(&szName, TEXT(sys->psz_name), nameLen);
szName[NAME_MAX - 1] = '\0';
if (b_create)
{
sys->handle = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
len, // maximum object size (low-order DWORD)
szName); // name of mapping object
}
else
{
sys->handle = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, szName);
}
if (sys->handle == NULL)
{
msg_Err(aout, "could not %s file mapping object (%ld).",
b_create ? "create": "open", GetLastError());
return VLC_EGENERIC;
}
sys->header = (amem_shared_t *) MapViewOfFile(sys->handle,
FILE_MAP_ALL_ACCESS, 0, 0, len);
if (sys->header == NULL)
{
msg_Err(aout, "could not map view of file (%ld).", GetLastError());
CloseHandle(sys->handle);
return VLC_EGENERIC;
}
#else
int flags = b_create ? (O_CREAT | O_EXCL | O_RDWR) : O_RDWR;
sys->shm_fd = shm_open(sys->psz_name, flags, (S_IREAD | S_IWRITE));
if (sys->shm_fd < 0)
{
msg_Err(aout, "could not %s shared memory object (%m)",
b_create ? "create" : "open");
return VLC_EGENERIC;
}
/* set correct maximum size */
if (ftruncate(sys->shm_fd, len) < 0)
{
msg_Err(aout, "failed setting shared memory size (%m)");
return VLC_EGENERIC;
}
/* mmap shared segment */
sys->header = mmap(0, len, (PROT_READ | PROT_WRITE),
MAP_SHARED, sys->shm_fd, 0);
if (sys->header == MAP_FAILED)
{
msg_Err(aout, "could not mmap shared memory object (%m)");
return VLC_EGENERIC;
}
#endif
size_t buflen = (aout->output.i_nb_samples * aout->output.output.i_bitspersample);
/* setup pointers in shared structures */
sys->header->i_buffers = i_buffers;
for (unsigned int i = 0; i < sys->header->i_buffers; i++)
{
amem_buffer_t *buffer = amem_buffer_get(aout, i);
if (buffer)
{
buffer->i_nb_samples = 0;
if (b_create)
memset(buffer->samples, 0, buflen);
}
}
/* initialize unamed semaphore */
if (b_create)
{
amem_sem_init(&sys->header->sem, 1);
sys->b_own_semaphore = true;
}
msg_Dbg(aout, "shared memory %s", b_create ? "created" : "opened");
return VLC_SUCCESS;
}
static void amem_shmem_close(aout_instance_t *aout)
{
aout_sys_t *sys = (aout_sys_t *)aout->output.p_sys;
if (sys->b_own_semaphore)
amem_sem_destroy(&sys->header->sem);
#if defined(_WIN32_WINNT) || defined(WIN32)
if (sys->header)
UnmapViewOfFile(sys->header);
CloseHandle(sys->handle);
#else
/* Determine shared memory size */
if (sys->header != MAP_FAILED)
{
ssize_t len = sizeof(amem_shared_t) + sys->header->i_buffers * (sizeof(amem_buffer_t)
+ aout->output.i_nb_samples * aout->output.output.i_bitspersample);
if (munmap(sys->header, len) < 0)
{
msg_Err(aout, "failed unmapping shared memory (%m)");
}
}
close(sys->shm_fd);
#endif
msg_Dbg(aout, "shared memory closed");
}
static void amem_shmem_header(aout_instance_t *aout)
{
aout_sys_t *sys = (aout_sys_t *)aout->output.p_sys;
amem_shared_t *header = sys->header;
msg_Dbg(aout, "syncing shared header");
amem_sem_wait(&header->sem);
/* Fill header struct */
#if defined(_WIN32_WINNT) || defined(WIN32)
uint16_t tag = 0;
fourcc_to_wf_tag(aout->output.output.i_format, &tag);
header->wave.wFormatTag = tag;
header->wave.nChannels = aout->output.output.i_channels;
header->wave.nSamplesPerSec = aout->output.output.i_rate * aout->output.i_nb_samples;
header->wave.wBitsPerSample = aout->output.output.i_bitspersample;
header->wave.nBlockAlign = header->wave.wBitsPerSample / 8 * header->wave.nChannels;
header->wave.nAvgBytesPerSec = header->wave.nSamplesPerSec * header->wave.nBlockAlign;
header->wave.cbSize = 0; /* No additional format metadata */
#else
header->i_format = aout->output.output.i_format;
header->i_bitspersample = aout->output.output.i_bitspersample;
header->i_bytes_per_frame = aout->output.output.i_bytes_per_frame;
header->i_channels = aout->output.output.i_channels;
header->i_frame_length = aout->output.output.i_frame_length;
header->i_rate = aout->output.output.i_rate;
header->i_blockalign = aout->output.output.i_blockalign;
#endif
header->i_read = header->i_write = 0;
header->i_read_samples = header->i_write_samples = 0;
header->i_max_samples = aout->output.i_nb_samples;
amem_sem_post(&header->sem);
}
static int amem_shmem_write(aout_instance_t *aout, aout_buffer_t *p_buffer)
{
aout_sys_t *sys = (aout_sys_t *)aout->output.p_sys;
amem_sem_wait(&sys->header->sem);
amem_buffer_t *buf = amem_buffer_get(aout, sys->header->i_write);
if (buf == NULL)
{
amem_sem_post(&sys->header->sem);
return VLC_EGENERIC;
}
assert(p_buffer->i_nb_samples <= sys->header->i_max_samples);
#if defined(_WIN32_WINNT) || defined(WIN32)
CopyMemory((PVOID)buf->samples, p_buffer->p_buffer, p_buffer->i_buffer);
#else
memcpy(buf->samples, p_buffer->p_buffer, p_buffer->i_buffer);
#endif
buf->i_nb_samples = p_buffer->i_nb_samples;
sys->header->i_write_samples += p_buffer->i_nb_samples;
sys->header->i_write = (sys->header->i_write + 1) % sys->header->i_buffers;
amem_sem_post(&sys->header->sem);
return VLC_SUCCESS;
}
/*****************************************************************************
* Module descriptor
*****************************************************************************/
#define ARATE_TEXT N_("Audio sample rate")
#define ARATE_LONGTEXT N_( \
"Sample rate of the audio stream (11250, 22500, 44100 or 48000).")
#define ACHANS_TEXT N_("Audio channels")
#define ACHANS_LONGTEXT N_( \
"Number of audio channels in the audio output." )
#define ABUFS_TEXT N_("Number of shared audio buffers")
#define ABUFS_LONGTEXT N_( \
"Number of shared audio buffers to use.")
#define AFORMAT_TEXT N_("Output format")
#define AFORMAT_LONGTEXT N_( \
"One of \"u8\", \"s8\", \"u16\", \"s16\", " \
"\"u16_le\", \"s16_le\", \"u16_be\", \"s16_be\", \"fixed32\", " \
"\"float32\" or \"spdif\"")
#define ADELAY_TEXT N_("Output delay")
#define ADELAY_LONGTEXT N_( \
"Initial delay for audio output" )
#define ANAME_TEXT N_("Name to use (default /amem.shared)")
#define ANAME_LONGTEXT N_( \
"Name to use as identifier to the (named) shared memory" )
#define AMEM_CFG_PREFIX "amem-"
vlc_module_begin()
set_description(N_("Audio memory output"))
set_shortname(N_("Audio memory"))
set_category( CAT_AUDIO )
set_subcategory( SUBCAT_AUDIO_AOUT )
set_capability("audio output", 0)
add_string( AMEM_CFG_PREFIX "name", "/amem.shared", NULL, ANAME_TEXT,
ANAME_LONGTEXT, true )
add_string( AMEM_CFG_PREFIX "format", "s16", NULL, AFORMAT_TEXT,
AFORMAT_LONGTEXT, true )
change_string_list( format_list, 0, 0 )
add_integer( AMEM_CFG_PREFIX "buffers", 2, NULL, ABUFS_TEXT,
ABUFS_LONGTEXT, false )
add_integer( AMEM_CFG_PREFIX "channels", 2, NULL, ACHANS_TEXT,
ACHANS_LONGTEXT, false )
change_integer_range( 0, CHANNELS_MAX )
add_integer( AMEM_CFG_PREFIX "samplerate", 44100, NULL, ARATE_TEXT,
ARATE_LONGTEXT, true )
add_integer( AMEM_CFG_PREFIX "delay", -1, NULL, ADELAY_TEXT,
ADELAY_TEXT, true )
set_callbacks(Open, Close)
vlc_module_end()
/*****************************************************************************
* Open
*****************************************************************************/
static int Open(vlc_object_t *p_this)
{
aout_instance_t *aout = (aout_instance_t*) p_this;
aout_sys_t *sys;
char *psz_format;
const char * const *ppsz_compare = format_list;
int i = 0;
/* */
aout->output.p_sys = sys = (aout_sys_t *) malloc(sizeof(aout_sys_t));
if (sys == NULL)
return VLC_ENOMEM;
/* */
sys->header = NULL;
sys->b_own_semaphore = false;
/* */
aout->output.pf_play = Play;
/* Name to use */
sys->psz_name = var_CreateGetString(p_this, AMEM_CFG_PREFIX "name");
/* Audio sample rate */
aout->output.output.i_rate = var_CreateGetInteger(p_this, AMEM_CFG_PREFIX "samplerate");
/* Audio format */
psz_format = var_CreateGetString(p_this, AMEM_CFG_PREFIX "format");
while (*ppsz_compare != NULL)
{
if (!strncmp(*ppsz_compare, psz_format, strlen(*ppsz_compare)))
{
break;
}
ppsz_compare++; i++;
}
if (*ppsz_compare == NULL)
{
msg_Err(aout, "cannot understand the format string (%s)", psz_format);
free(aout->output.p_sys->psz_name);
free(aout->output.p_sys);
free(psz_format);
return VLC_EGENERIC;
}
free(psz_format);
aout->output.output.i_format = format_int[i];
aout->output.output.i_bitspersample = aout_BitsPerSample(aout->output.output.i_format);
if (AOUT_FMT_NON_LINEAR(&aout->output.output))
{
aout->output.i_nb_samples = A52_FRAME_NB;
aout->output.output.i_bytes_per_frame = AOUT_SPDIF_SIZE;
aout->output.output.i_frame_length = A52_FRAME_NB;
if ((aout->output.output.i_bitspersample == 0) &&
(aout->output.output.i_format == VLC_CODEC_SPDIFL))
aout->output.output.i_bitspersample = 32;
aout_VolumeNoneInit(aout);
}
else
{
aout->output.i_nb_samples = FRAME_SIZE;
aout_VolumeSoftInit(aout);
}
/* Channels number */
int i_channels = var_CreateGetInteger(p_this, AMEM_CFG_PREFIX "channels");
if ((i_channels > 0) && (i_channels <= CHANNELS_MAX))
{
aout->output.output.i_physical_channels =
pi_channels_maps[i_channels];
}
/* Audio output buffers */
int i_buffers = var_CreateGetInteger(aout, AMEM_CFG_PREFIX "buffers");
if (i_buffers < 1)
{
msg_Err(aout, "insufficient shared audio buffers requested");
free(aout->output.p_sys->psz_name);
free(aout->output.p_sys);
return VLC_EGENERIC;
}
/* Audio output delay */
int64_t i_delay = var_CreateGetInteger(aout, AMEM_CFG_PREFIX "delay");
if (i_delay >= 0)
{
playlist_t *p_playlist = pl_Get(aout);
/* Update the input */
input_thread_t *p_input = playlist_CurrentInput(p_playlist);
if (p_input == NULL)
{
free(aout->output.p_sys->psz_name);
free(aout->output.p_sys);
return VLC_EGENERIC;
}
if (var_GetTime(p_input, "audio-delay") != i_delay)
var_SetTime(p_input, "audio-delay", i_delay);
vlc_object_release(p_input);
}
/* Setup shared memory connection */
if (SetupSharedMem(aout, i_buffers) != VLC_SUCCESS)
{
Close(p_this);
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/*****************************************************************************
* Close
*****************************************************************************/
static void Close(vlc_object_t *p_this)
{
aout_instance_t *aout = (aout_instance_t*) p_this;
ExitSharedMem(aout);
free(aout->output.p_sys->psz_name);
free(aout->output.p_sys);
}
/*****************************************************************************
* Play
*****************************************************************************/
static void Play(aout_instance_t *aout)
{
aout_buffer_t *p_buffer;
p_buffer = aout_FifoPop(aout, &aout->output.fifo);
if (p_buffer == NULL)
return;
if (amem_shmem_write(aout, p_buffer) != VLC_SUCCESS)
{
msg_Err(aout, "write error (%m)");
}
aout_BufferFree(p_buffer);
}
/*****************************************************************************
* Shared Memory
*****************************************************************************/
static int SetupSharedMem(aout_instance_t *aout, unsigned int i_buffers)
{
aout_sys_t *sys = (aout_sys_t *)aout->output.p_sys;
/* Determine shared memory size */
ssize_t buflen = sizeof(amem_buffer_t) +
(aout->output.i_nb_samples * aout->output.output.i_bitspersample);
ssize_t len = sizeof(amem_shared_t) + (i_buffers * buflen);
if (*sys->psz_name != '/')
{
msg_Err(aout, "invalid shared segment name (does not begin with /)");
return VLC_EGENERIC;
}
if (strlen(sys->psz_name) >= NAME_MAX)
{
msg_Err(aout, "invalid shared segment name (string length >= %d)", NAME_MAX);
return VLC_EGENERIC;
}
if (amem_shmem_open(aout, i_buffers, len, false) != VLC_SUCCESS)
return VLC_EGENERIC;
/* Fill header struct */
amem_shmem_header(aout);
return VLC_SUCCESS;
}
static void ExitSharedMem(aout_instance_t *aout)
{
amem_shmem_close(aout);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment