C part of the plugin.

This commit is contained in:
Johns 2011-12-07 15:05:38 +01:00
parent ab6c3b4de8
commit ce97b938ca
11 changed files with 6971 additions and 0 deletions

930
audio.c Normal file
View File

@ -0,0 +1,930 @@
///
/// @file audio.c @brief Audio module
///
/// Copyright (c) 2009 - 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
///
/// @defgroup Audio The audio module.
///
/// This module contains all audio output functions.
///
/// ALSA PCM api is used.
/// @see http://www.alsa-project.org/alsa-doc/alsa-lib
///
/// alsa async playback is broken, don't use it!
///
#define USE_AUDIO_THREAD
#include <stdio.h>
#include <stdint.h>
#include <libintl.h>
#define _(str) gettext(str) ///< gettext shortcut
#define _N(str) str ///< gettext_noop shortcut
#include <alsa/asoundlib.h>
#ifdef USE_AUDIO_THREAD
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <pthread.h>
#endif
#include "ringbuffer.h"
#include "misc.h"
#include "audio.h"
//----------------------------------------------------------------------------
// Variables
//----------------------------------------------------------------------------
static const char *AudioPCMDevice; ///< alsa PCM device name
static const char *AudioMixerDevice; ///< alsa mixer device name
static volatile char AudioRunning; ///< thread running / stopped
static int AudioPaused; ///< audio paused
static unsigned AudioSampleRate; ///< audio sample rate in hz
static unsigned AudioChannels; ///< number of audio channels
static uint64_t AudioPTS; ///< audio pts clock
//----------------------------------------------------------------------------
// Alsa variables
//----------------------------------------------------------------------------
static snd_pcm_t *AlsaPCMHandle; ///< alsa pcm handle
static char AlsaCanPause; ///< hw supports pause
static int AlsaUseMmap; ///< use mmap
static RingBuffer *AlsaRingBuffer; ///< audio ring buffer
static unsigned AlsaStartThreshold; ///< start play, if filled
static snd_mixer_t *AlsaMixer; ///< alsa mixer handle
static snd_mixer_elem_t *AlsaMixerElem; ///< alsa pcm mixer element
static int AlsaRatio; ///< internal -> mixer ratio * 1000
//----------------------------------------------------------------------------
// alsa pcm
//----------------------------------------------------------------------------
/**
** Place samples in ringbuffer.
**
** @param samples sample buffer
** @param count number of bytes in sample buffer
**
** @returns true if play should be started.
*/
static int AlsaAddToRingbuffer(const void *samples, int count)
{
int n;
n = RingBufferWrite(AlsaRingBuffer, samples, count);
if (n != count) {
Error(_("audio/alsa: can't place %d samples in ring buffer\n"), count);
// too many bytes are lost
}
// Update audio clock
AudioPTS += (count * 90000) / (AudioSampleRate * AudioChannels * 2);
if (!AudioRunning) {
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
// restart play-back
return 1;
}
}
return 0;
}
/**
** Play samples from ringbuffer.
*/
static int AlsaPlayRingbuffer(void)
{
int first;
int avail;
int n;
int err;
int frames;
const void *p;
first = 1;
for (;;) {
// how many bytes can be written?
n = snd_pcm_avail_update(AlsaPCMHandle);
if (n < 0) {
if (n == -EAGAIN) {
continue;
}
Error(_("audio/alsa: underrun error?\n"));
err = snd_pcm_recover(AlsaPCMHandle, n, 0);
if (err >= 0) {
continue;
}
Error(_("audio/alsa: snd_pcm_avail_update(): %s\n"),
snd_strerror(n));
return -1;
}
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, n);
if (avail < 256) { // too much overhead
if (first) {
// happens with broken alsa drivers
Error(_("audio/alsa: broken driver %d\n"), avail);
}
break;
}
n = RingBufferGetReadPointer(AlsaRingBuffer, &p);
if (!n) { // ring buffer empty
if (first) { // only error on first loop
return 1;
}
return 0;
}
if (n < avail) { // not enough bytes in ring buffer
avail = n;
}
if (!avail) { // full or buffer empty
break;
}
frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail);
again:
if (AlsaUseMmap) {
err = snd_pcm_mmap_writei(AlsaPCMHandle, p, frames);
} else {
err = snd_pcm_writei(AlsaPCMHandle, p, frames);
}
Debug(4, "audio/alsa: wrote %d/%d frames\n", err, frames);
if (err < 0) {
if (err == -EAGAIN) {
goto again;
}
Error(_("audio/alsa: underrun error?\n"));
err = snd_pcm_recover(AlsaPCMHandle, err, 0);
if (err >= 0) {
goto again;
}
Error(_("audio/alsa: snd_pcm_writei failed: %s\n"),
snd_strerror(err));
return -1;
}
if (err != frames) {
// this could happen, if underrun happened
Error(_("audio/alsa: error not all frames written\n"));
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, err);
}
RingBufferReadAdvance(AlsaRingBuffer, avail);
first = 0;
}
return 0;
}
#if 0
// async playback is broken, don't use it!
//----------------------------------------------------------------------------
// async playback
//----------------------------------------------------------------------------
/**
** Alsa async pcm callback function.
**
** @param handler alsa async handler
*/
static void AlsaAsyncCallback(snd_async_handler_t * handler)
{
Debug(3, "audio/%s: %p\n", __FUNCTION__, handler);
// how many bytes can be written?
for (;;) {
n = snd_pcm_avail_update(AlsaPCMHandle);
if (n < 0) {
Error(_("audio/alsa: snd_pcm_avail_update(): %s\n"),
snd_strerror(n));
break;
}
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, n);
if (avail < 512) { // too much overhead
break;
}
n = RingBufferGetReadPointer(AlsaRingBuffer, &p);
if (!n) { // ring buffer empty
Debug(3, "audio/alsa: ring buffer empty\n");
break;
}
if (n < avail) { // not enough bytes in ring buffer
avail = n;
}
if (!avail) { // full
break;
}
frames = snd_pcm_bytes_to_frames(AlsaPCMHandle, avail);
again:
if (AlsaUseMmap) {
err = snd_pcm_mmap_writei(AlsaPCMHandle, p, frames);
} else {
err = snd_pcm_writei(AlsaPCMHandle, p, frames);
}
Debug(3, "audio/alsa: %d => %d\n", frames, err);
if (err < 0) {
Error(_("audio/alsa: underrun error?\n"));
err = snd_pcm_recover(AlsaPCMHandle, err, 0);
if (err >= 0) {
goto again;
}
Error(_("audio/alsa: snd_pcm_writei failed: %s\n"),
snd_strerror(err));
}
if (err != frames) {
Error(_("audio/alsa: error not all frames written\n"));
avail = snd_pcm_frames_to_bytes(AlsaPCMHandle, err);
}
RingBufferReadAdvance(AlsaRingBuffer, avail);
}
}
/**
** Place samples in audio output queue.
**
** @param samples sample buffer
** @param count number of bytes in sample buffer
*/
void AudioEnqueue(const void *samples, int count)
{
snd_pcm_state_t state;
int n;
//int err;
Debug(3, "audio: %6zd + %4d\n", RingBufferUsedBytes(AlsaRingBuffer),
count);
n = RingBufferWrite(AlsaRingBuffer, samples, count);
if (n != count) {
Fatal(_("audio: can't place %d samples in ring buffer\n"), count);
}
// check if running, wait until enough buffered
state = snd_pcm_state(AlsaPCMHandle);
if (state == SND_PCM_STATE_PREPARED) {
Debug(3, "audio/alsa: state %d - %s\n", state,
snd_pcm_state_name(state));
// FIXME: adjust start ratio
if (RingBufferFreeBytes(AlsaRingBuffer)
< RingBufferUsedBytes(AlsaRingBuffer)) {
// restart play-back
#if 0
if (AlsaCanPause) {
if ((err = snd_pcm_pause(AlsaPCMHandle, 0))) {
Error(_("audio: snd_pcm_pause(): %s\n"),
snd_strerror(err));
}
} else {
if ((err = snd_pcm_prepare(AlsaPCMHandle)) < 0) {
Error(_("audio: snd_pcm_prepare(): %s\n"),
snd_strerror(err));
}
}
if ((err = snd_pcm_prepare(AlsaPCMHandle)) < 0) {
Error(_("audio: snd_pcm_prepare(): %s\n"), snd_strerror(err));
}
Debug(3, "audio/alsa: unpaused\n");
if ((err = snd_pcm_start(AlsaPCMHandle)) < 0) {
Error(_("audio: snd_pcm_start(): %s\n"), snd_strerror(err));
}
#endif
state = snd_pcm_state(AlsaPCMHandle);
Debug(3, "audio/alsa: state %d - %s\n", state,
snd_pcm_state_name(state));
Debug(3, "audio/alsa: unpaused\n");
AudioPaused = 0;
}
}
// Update audio clock
// AudioPTS += (size * 90000) / (AudioSampleRate * AudioChannels * 2);
}
#endif
//----------------------------------------------------------------------------
// thread playback
//----------------------------------------------------------------------------
#ifdef USE_AUDIO_THREAD
static pthread_t AudioThread; ///< audio play thread
static pthread_cond_t AudioStartCond; ///< condition variable
static pthread_mutex_t AudioMutex; ///< audio condition mutex
/**
** Audio play thread.
*/
static void *AudioPlayHandlerThread(void *dummy)
{
int err;
Debug(3, "audio: play thread started\n");
for (;;) {
Debug(3, "audio: wait on start condition\n");
pthread_mutex_lock(&AudioMutex);
AudioRunning = 0;
do {
pthread_cond_wait(&AudioStartCond, &AudioMutex);
// cond_wait can return, without signal!
} while (!AudioRunning);
pthread_mutex_unlock(&AudioMutex);
Debug(3, "audio: play start\n");
for (;;) {
Debug(4, "audio: play loop\n");
pthread_testcancel();
if ((err = snd_pcm_wait(AlsaPCMHandle, 100)) < 0) {
Error(_("audio/alsa: wait underrun error?\n"));
err = snd_pcm_recover(AlsaPCMHandle, err, 0);
if (err >= 0) {
continue;
}
Error(_("audio/alsa: snd_pcm_wait(): %s\n"),
snd_strerror(err));
usleep(100 * 1000);
continue;
}
if ((err = AlsaPlayRingbuffer())) { // empty / error
snd_pcm_state_t state;
if (err < 0) { // underrun error
break;
}
state = snd_pcm_state(AlsaPCMHandle);
if (state != SND_PCM_STATE_RUNNING) {
Debug(3, "audio/alsa: stopping play\n");
break;
}
usleep(20 * 1000);
}
}
}
return dummy;
}
/**
** Place samples in audio output queue.
**
** @param samples sample buffer
** @param count number of bytes in sample buffer
*/
void AudioEnqueue(const void *samples, int count)
{
if (AlsaAddToRingbuffer(samples, count)) {
snd_pcm_state_t state;
state = snd_pcm_state(AlsaPCMHandle);
Debug(3, "audio/alsa: enqueue state %s\n", snd_pcm_state_name(state));
// no lock needed, can wakeup next time
AudioRunning = 1;
pthread_cond_signal(&AudioStartCond);
}
}
#endif
//----------------------------------------------------------------------------
// direct playback
//----------------------------------------------------------------------------
#if 0
// direct play produces underuns on some hardware
/**
** Place samples in audio output queue.
**
** @param samples sample buffer
** @param count number of bytes in sample buffer
*/
void AudioEnqueue(const void *samples, int count)
{
snd_pcm_state_t state;
int avail;
int n;
int err;
int frames;
const void *p;
Debug(3, "audio/alsa: %6zd + %4d\n", RingBufferUsedBytes(AlsaRingBuffer),
count);
n = RingBufferWrite(AlsaRingBuffer, samples, count);
if (n != count) {
Error(_("audio/alsa: can't place %d samples in ring buffer\n"), count);
}
// check if running, wait until enough buffered
state = snd_pcm_state(AlsaPCMHandle);
Debug(4, "audio/alsa: state %d - %s\n", state, snd_pcm_state_name(state));
if (state == SND_PCM_STATE_PREPARED) {
// FIXME: adjust start ratio
if (RingBufferFreeBytes(AlsaRingBuffer)
> RingBufferUsedBytes(AlsaRingBuffer)) {
return;
}
Debug(3, "audio/alsa: state %d - %s start play\n", state,
snd_pcm_state_name(state));
}
// Update audio clock
AudioPTS += (size * 90000) / (AudioSampleRate * AudioChannels * 2);
}
#endif
/**
** Initialize alsa pcm device.
**
** @see AudioPCMDevice
*/
static void AlsaInitPCM(void)
{
const char *device;
snd_pcm_t *handle;
snd_pcm_hw_params_t *hw_params;
int err;
snd_pcm_uframes_t buffer_size;
if (!(device = AudioPCMDevice)) {
if (!(device = getenv("ALSA_DEVICE"))) {
device = "default";
}
}
// FIXME: must set alsa error output to /dev/null
if ((err =
snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK,
SND_PCM_NONBLOCK)) < 0) {
Fatal(_("audio/alsa: playback open '%s' error: %s\n"), device,
snd_strerror(err));
}
AlsaPCMHandle = handle;
if ((err = snd_pcm_nonblock(handle, SND_PCM_NONBLOCK)) < 0) {
Error(_("audio/alsa: can't set block mode: %s\n"), snd_strerror(err));
}
snd_pcm_hw_params_alloca(&hw_params);
// choose all parameters
if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
Error(_
("audio: snd_pcm_hw_params_any: no configurations available: %s\n"),
snd_strerror(err));
}
AlsaCanPause = snd_pcm_hw_params_can_pause(hw_params);
Info(_("audio/alsa: hw '%s' supports pause: %s\n"), device,
AlsaCanPause ? "yes" : "no");
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
Info(_("audio/alsa: max buffer size %lu\n"), buffer_size);
pthread_mutex_init(&AudioMutex, NULL);
pthread_cond_init(&AudioStartCond, NULL);
pthread_create(&AudioThread, NULL, AudioPlayHandlerThread, NULL);
pthread_detach(AudioThread);
}
//----------------------------------------------------------------------------
// Alsa Mixer
//----------------------------------------------------------------------------
/**
** Set mixer volume (0-100)
**
** @param volume volume (0 .. 100)
*/
void AudioSetVolume(int volume)
{
int v;
if (AlsaMixer && AlsaMixerElem) {
v = (volume * AlsaRatio) / 1000;
snd_mixer_selem_set_playback_volume(AlsaMixerElem, 0, v);
snd_mixer_selem_set_playback_volume(AlsaMixerElem, 1, v);
}
}
/**
** Initialize alsa mixer.
*/
static void AlsaInitMixer(void)
{
const char *device;
snd_mixer_t *alsa_mixer;
snd_mixer_elem_t *alsa_mixer_elem;
long alsa_mixer_elem_min;
long alsa_mixer_elem_max;
if (!(device = AudioMixerDevice)) {
if (!(device = getenv("ALSA_MIXER"))) {
device = "default";
}
}
Debug(3, "audio/alsa: mixer open\n");
snd_mixer_open(&alsa_mixer, 0);
if (alsa_mixer && snd_mixer_attach(alsa_mixer, device) >= 0
&& snd_mixer_selem_register(alsa_mixer, NULL, NULL) >= 0
&& snd_mixer_load(alsa_mixer) >= 0) {
const char *const alsa_mixer_elem_name = "PCM";
alsa_mixer_elem = snd_mixer_first_elem(alsa_mixer);
while (alsa_mixer_elem) {
const char *name;
name = snd_mixer_selem_get_name(alsa_mixer_elem);
if (strcasecmp(name, alsa_mixer_elem_name) == 0) {
snd_mixer_selem_get_playback_volume_range(alsa_mixer_elem,
&alsa_mixer_elem_min, &alsa_mixer_elem_max);
AlsaRatio =
(1000 * (alsa_mixer_elem_max - alsa_mixer_elem_min)) / 100;
Debug(3, "audio/alsa: PCM mixer found %ld - %ld ratio %d\n",
alsa_mixer_elem_min, alsa_mixer_elem_max, AlsaRatio);
break;
}
alsa_mixer_elem = snd_mixer_elem_next(alsa_mixer_elem);
}
AlsaMixer = alsa_mixer;
AlsaMixerElem = alsa_mixer_elem;
} else {
Error(_("audio/alsa: can't open alsa mixer '%s'\n"), device);
}
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
/**
** Get audio delay in time stamps.
*/
uint64_t AudioGetDelay(void)
{
int err;
snd_pcm_sframes_t delay;
if ((err = snd_pcm_delay(AlsaPCMHandle, &delay)) < 0) {
//Debug(3, "audio/alsa: no hw delay\n");
delay = 0UL;
} else if (snd_pcm_state(AlsaPCMHandle) != SND_PCM_STATE_RUNNING) {
//Debug(3, "audio/alsa: %lu delay ok, but not running\n", delay);
}
delay = (delay * 90000) / AudioSampleRate;
//Debug(3, "audio/alsa: hw delay %lu\n", delay);
delay +=
(RingBufferUsedBytes(AlsaRingBuffer) * 90000) / (AudioSampleRate *
AudioChannels);
//Debug(3, "audio/alsa: hw+sw delay %lu ms\n", delay / 90);
return delay;
}
/**
** Setup audio for requested format.
**
** @param freq sample frequency
** @param channels number of channels
**
** @todo audio changes must be queued and done when the buffer is empty
*/
void AudioSetup(int freq, int channels)
{
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
int err;
#if 1
Debug(3, "audio/alsa: channels %d frequency %d hz\n", channels, freq);
if (!freq || !channels) { // invalid parameter
// FIXME: set flag invalid setup
return;
}
AudioChannels = channels;
AudioSampleRate = freq;
// FIXME: thread!!
RingBufferReadAdvance(AlsaRingBuffer, RingBufferUsedBytes(AlsaRingBuffer));
if ((err =
snd_pcm_set_params(AlsaPCMHandle, SND_PCM_FORMAT_S16,
AlsaUseMmap ? SND_PCM_ACCESS_MMAP_INTERLEAVED :
SND_PCM_ACCESS_RW_INTERLEAVED, channels, freq, 1,
125 * 1000))) {
Error(_("audio/alsa: set params error: %s\n"), snd_strerror(err));
if (channels == 2) {
// FIXME: must stop sound
return;
}
// FIXME: enable channel downmix
// AudioChannels = downmix_channels;
return;
}
#else
snd_pcm_hw_params_t *hw_params;
int dir;
unsigned buffer_time;
snd_pcm_uframes_t buffer_size;
Debug(3, "audio/alsa: channels %d frequency %d hz\n", channels, freq);
snd_pcm_hw_params_alloca(&hw_params);
// choose all parameters
if ((err = snd_pcm_hw_params_any(AlsaPCMHandle, hw_params)) < 0) {
Error(_
("audio: snd_pcm_hw_params_any: no configurations available: %s\n"),
snd_strerror(err));
}
if ((err =
snd_pcm_hw_params_set_rate_resample(AlsaPCMHandle, hw_params, 1))
< 0) {
Error(_("audio: can't set rate resample: %s\n"), snd_strerror(err));
}
if ((err =
snd_pcm_hw_params_set_format(AlsaPCMHandle, hw_params,
SND_PCM_FORMAT_S16)) < 0) {
Error(_("audio: can't set 16-bit: %s\n"), snd_strerror(err));
}
if ((err =
snd_pcm_hw_params_set_access(AlsaPCMHandle, hw_params,
SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
Error(_("audio: can't set interleaved read/write %s\n"),
snd_strerror(err));
}
if ((err =
snd_pcm_hw_params_set_channels(AlsaPCMHandle, hw_params,
channels)) < 0) {
Error(_("audio: can't set channels: %s\n"), snd_strerror(err));
}
if ((err =
snd_pcm_hw_params_set_rate(AlsaPCMHandle, hw_params, freq,
0)) < 0) {
Error(_("audio: can't set rate: %s\n"), snd_strerror(err));
}
// 500000
// 170667us
buffer_time = 1000 * 1000 * 1000;
dir = 1;
#if 0
snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time, &dir);
Info(_("audio/alsa: %dus max buffer time\n"), buffer_time);
buffer_time = 5 * 200 * 1000; // 1s
if ((err =
snd_pcm_hw_params_set_buffer_time_near(AlsaPCMHandle, hw_params,
&buffer_time, &dir)) < 0) {
Error(_("audio: snd_pcm_hw_params_set_buffer_time_near failed: %s\n"),
snd_strerror(err));
}
Info(_("audio/alsa: %dus buffer time\n"), buffer_time);
#endif
snd_pcm_hw_params_get_buffer_size_max(hw_params, &buffer_size);
Info(_("audio/alsa: buffer size %lu\n"), buffer_size);
buffer_size = buffer_size < 65536 ? buffer_size : 65536;
if ((err =
snd_pcm_hw_params_set_buffer_size_near(AlsaPCMHandle, hw_params,
&buffer_size))) {
Error(_("audio: can't set buffer size: %s\n"), snd_strerror(err));
}
Info(_("audio/alsa: buffer size %lu\n"), buffer_size);
if ((err = snd_pcm_hw_params(AlsaPCMHandle, hw_params)) < 0) {
Error(_("audio: snd_pcm_hw_params failed: %s\n"), snd_strerror(err));
}
// FIXME: use hw_params for buffer_size period_size
#endif
// update buffer
snd_pcm_get_params(AlsaPCMHandle, &buffer_size, &period_size);
Info(_("audio/alsa: buffer size %lu, period size %lu\n"), buffer_size,
period_size);
Debug(3, "audio/alsa: state %s\n",
snd_pcm_state_name(snd_pcm_state(AlsaPCMHandle)));
AlsaStartThreshold =
snd_pcm_frames_to_bytes(AlsaPCMHandle, buffer_size + period_size);
// min 500ms
if (AlsaStartThreshold < (freq * channels * 2U) / 2) {
AlsaStartThreshold = (freq * channels * 2U) / 2;
}
Debug(3, "audio/alsa: delay %u ms\n", (AlsaStartThreshold * 1000)
/ (AudioSampleRate * AudioChannels * 2));
}
/**
** Set alsa pcm audio device.
**
** @param device name of pcm device (fe. "hw:0,9")
*/
void AudioSetDevice(const char *device)
{
AudioPCMDevice = device;
}
/**
** Initialize audio output module.
*/
void AudioInit(void)
{
AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
AlsaInitPCM();
AlsaInitMixer();
AudioSetup(48000, 2); // set default parameters
AudioPaused = 1;
}
/**
** Cleanup audio output module.
*/
void AudioExit(void)
{
void *retval;
pthread_cancel(AudioThread);
pthread_join(AudioThread, &retval);
if (retval != PTHREAD_CANCELED) {
Error(_("audio: can't cancel alsa play thread\n"));
}
pthread_cond_destroy(&AudioStartCond);
pthread_mutex_destroy(&AudioMutex);
if (AlsaPCMHandle) {
snd_pcm_close(AlsaPCMHandle);
AlsaPCMHandle = NULL;
}
if (AlsaMixer) {
snd_mixer_close(AlsaMixer);
AlsaMixer = NULL;
AlsaMixerElem = NULL;
}
if (AlsaRingBuffer) {
RingBufferDel(AlsaRingBuffer);
AlsaRingBuffer = NULL;
}
}
//----------------------------------------------------------------------------
// Test
//----------------------------------------------------------------------------
void AudioTest(void)
{
for (;;) {
unsigned u;
uint8_t buffer[16 * 1024]; // some random data
for (u = 0; u < sizeof(buffer); u++) {
buffer[u] = random() & 0xffff;
}
Debug(3, "audio/test: loop\n");
for (;;) {
while (RingBufferFreeBytes(AlsaRingBuffer) > sizeof(buffer)) {
AudioEnqueue(buffer, sizeof(buffer));
}
}
}
}
#ifdef AUDIO_TEST
#include <getopt.h>
int SysLogLevel; ///< show additional debug informations
/**
** Print version.
*/
static void PrintVersion(void)
{
printf("audio_test: audio tester Version " VERSION
#ifdef GIT_REV
"(GIT-" GIT_REV ")"
#endif
",\n\t(c) 2009 - 2011 by Johns\n"
"\tLicense AGPLv3: GNU Affero General Public License version 3\n");
}
/**
** Print usage.
*/
static void PrintUsage(void)
{
printf("Usage: audio_test [-?dhv]\n"
"\t-d\tenable debug, more -d increase the verbosity\n"
"\t-? -h\tdisplay this message\n" "\t-v\tdisplay version information\n"
"Only idiots print usage on stderr!\n");
}
/**
** Main entry point.
**
** @param argc number of arguments
** @param argv arguments vector
**
** @returns -1 on failures, 0 clean exit.
*/
int main(int argc, char *const argv[])
{
SysLogLevel = 0;
//
// Parse command line arguments
//
for (;;) {
switch (getopt(argc, argv, "hv?-c:d")) {
case 'd': // enabled debug
++SysLogLevel;
continue;
case EOF:
break;
case 'v': // print version
PrintVersion();
return 0;
case '?':
case 'h': // help usage
PrintVersion();
PrintUsage();
return 0;
case '-':
PrintVersion();
PrintUsage();
fprintf(stderr, "\nWe need no long options\n");
return -1;
case ':':
PrintVersion();
fprintf(stderr, "Missing argument for option '%c'\n", optopt);
return -1;
default:
PrintVersion();
fprintf(stderr, "Unkown option '%c'\n", optopt);
return -1;
}
break;
}
if (optind < argc) {
PrintVersion();
while (optind < argc) {
fprintf(stderr, "Unhandled argument '%s'\n", argv[optind++]);
}
return -1;
}
//
// main loop
//
AudioInit();
for (;;) {
unsigned u;
uint8_t buffer[16 * 1024]; // some random data
for (u = 0; u < sizeof(buffer); u++) {
buffer[u] = random() & 0xffff;
}
Debug(3, "audio/test: loop\n");
for (;;) {
while (RingBufferFreeBytes(AlsaRingBuffer) > sizeof(buffer)) {
AudioEnqueue(buffer, sizeof(buffer));
}
}
}
AudioExit();
return 0;
}
#endif

47
audio.h Normal file
View File

@ -0,0 +1,47 @@
///
/// @file audio.h @brief Audio module headerfile
///
/// Copyright (c) 2009 - 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup Audio
/// @{
//----------------------------------------------------------------------------
// Prototypes
//----------------------------------------------------------------------------
extern void AudioEnqueue(const void *, int); ///< buffer audio samples
//extern int AudioFreeBytes(void); ///< free bytes in audio output
//extern int AudioUsedBytes(void); ///< used bytes in audio output
//extern void AudioSetClock(int64_t); ///< set audio clock base
//extern int64_t AudioGetClock(); ///< get current audio clock
extern uint64_t AudioGetDelay(void); ///< get current audio delay
extern void AudioSetup(int, int); ///< setup audio output
//extern void AudioPlay(void); ///< play audio
//extern void AudioPause(void); ///< pause audio
extern void AudioSetVolume(int); ///< set volume
extern void AudioSetDevice(const char *); ///< set alsa PCM audio device
extern void AudioInit(void); ///< setup audio module
extern void AudioExit(void); ///< cleanup and exit audio module
/// @}

627
codec.c Normal file
View File

@ -0,0 +1,627 @@
///
/// @file codec.c @brief Codec functions
///
/// Copyright (c) 2009 - 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
///
/// @defgroup Codec The codec module.
///
/// This module contains all decoder and codec functions.
/// It is uses ffmpeg (http://ffmpeg.org) as backend.
///
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <libintl.h>
#define _(str) gettext(str) ///< gettext shortcut
#define _N(str) str ///< gettext_noop shortcut
#include <alsa/iatomic.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/vaapi.h>
#ifdef MAIN_H
#include MAIN_H
#endif
#include "misc.h"
#include "video.h"
#include "audio.h"
#include "codec.h"
//----------------------------------------------------------------------------
// Video
//----------------------------------------------------------------------------
#if 0
///
/// Video decoder typedef.
///
//typedef struct _video_decoder_ Decoder;
#endif
///
/// Video decoder structure.
///
struct _video_decoder_
{
VideoHwDecoder *HwDecoder; ///< video hardware decoder
AVCodec *VideoCodec; ///< video codec
AVCodecContext *VideoCtx; ///< video codec context
AVFrame *Frame; ///< decoded video frame
};
//----------------------------------------------------------------------------
// Call-backs
//----------------------------------------------------------------------------
static int CodecFfmpegOk; ///< check ffmpeg idiotics
/**
** Callback to negotiate the PixelFormat.
**
** @param fmt is the list of formats which are supported by the codec,
** it is terminated by -1 as 0 is a valid format, the
** formats are ordered by quality.
*/
static enum PixelFormat Codec_get_format(AVCodecContext * video_ctx,
const enum PixelFormat *fmt)
{
VideoDecoder *decoder;
decoder = video_ctx->opaque;
Debug(3, "codec: %s: %18p\n", __FUNCTION__, decoder);
CodecFfmpegOk = 1;
return Video_get_format(decoder->HwDecoder, video_ctx, fmt);
}
/**
** Video buffer management, get buffer for frame.
**
** Called at the beginning of each frame to get a buffer for it.
**
** @param video_ctx Codec context
** @param frame Get buffer for this frame
*/
static int Codec_get_buffer(AVCodecContext * video_ctx, AVFrame * frame)
{
if (!CodecFfmpegOk) { // get_format missing
enum PixelFormat fmts[2];
fprintf(stderr, "codec: buggy ffmpeg\n");
fmts[0] = video_ctx->pix_fmt;
fmts[1] = PIX_FMT_NONE;
Codec_get_format(video_ctx, fmts);
}
// VDPAU: PIX_FMT_VDPAU_H264 .. PIX_FMT_VDPAU_VC1 PIX_FMT_VDPAU_MPEG4
if ((PIX_FMT_VDPAU_H264 <= video_ctx->pix_fmt
&& video_ctx->pix_fmt <= PIX_FMT_VDPAU_VC1)
|| video_ctx->pix_fmt == PIX_FMT_VDPAU_MPEG4) {
VideoDecoder *decoder;
unsigned surface;
decoder = video_ctx->opaque;
surface = VideoGetSurface(decoder->HwDecoder);
//Debug(3, "codec: use surface %#010x\n", surface);
frame->type = FF_BUFFER_TYPE_USER;
frame->age = 256 * 256 * 256 * 64;
frame->data[0] = (void *)(size_t) surface;
// FIXME: reordered?
return 0;
}
// VA-API:
if (video_ctx->hwaccel_context) {
VideoDecoder *decoder;
unsigned surface;
decoder = video_ctx->opaque;
surface = VideoGetSurface(decoder->HwDecoder);
//Debug(3, "codec: use surface %#010x\n", surface);
frame->type = FF_BUFFER_TYPE_USER;
frame->age = 256 * 256 * 256 * 64;
// vaapi needs both fields set
frame->data[0] = (void *)(size_t) surface;
frame->data[3] = (void *)(size_t) surface;
// FIXME: reordered?
return 0;
}
//Debug(3, "codec: fallback to default get_buffer\n");
return avcodec_default_get_buffer(video_ctx, frame);
}
/**
** Video buffer management, release buffer for frame.
** Called to release buffers which were allocated with get_buffer.
**
** @param video_ctx Codec context
** @param frame Release buffer for this frame
*/
static void Codec_release_buffer(AVCodecContext * video_ctx, AVFrame * frame)
{
// VDPAU: PIX_FMT_VDPAU_H264 .. PIX_FMT_VDPAU_VC1 PIX_FMT_VDPAU_MPEG4
if ((PIX_FMT_VDPAU_H264 <= video_ctx->pix_fmt
&& video_ctx->pix_fmt <= PIX_FMT_VDPAU_VC1)
|| video_ctx->pix_fmt == PIX_FMT_VDPAU_MPEG4) {
VideoDecoder *decoder;
unsigned surface;
decoder = video_ctx->opaque;
surface = (unsigned)(size_t) frame->data[0];
//Debug(3, "codec: release surface %#010x\n", surface);
VideoReleaseSurface(decoder->HwDecoder, surface);
frame->data[0] = NULL;
return;
}
// VA-API
if (video_ctx->hwaccel_context) {
VideoDecoder *decoder;
unsigned surface;
decoder = video_ctx->opaque;
surface = (unsigned)(size_t) frame->data[3];
//Debug(3, "codec: release surface %#010x\n", surface);
VideoReleaseSurface(decoder->HwDecoder, surface);
frame->data[0] = NULL;
frame->data[3] = NULL;
return;
}
//Debug(3, "codec: fallback to default release_buffer\n");
return avcodec_default_release_buffer(video_ctx, frame);
}
//----------------------------------------------------------------------------
// Test
//----------------------------------------------------------------------------
/**
** Allocate a new video decoder context.
**
** @param hw_decoder video hardware decoder
**
** @returns private decoder pointer for audio/video decoder.
*/
VideoDecoder *CodecVideoNewDecoder(VideoHwDecoder * hw_decoder)
{
VideoDecoder *decoder;
if (!(decoder = calloc(1, sizeof(*decoder)))) {
Fatal(_("codec: Can't allocate vodeo decoder\n"));
}
decoder->HwDecoder = hw_decoder;
return decoder;
}
/**
** Open video decoder.
**
** @param decoder private video decoder
** @param name video codec name
** @param codec_id video codec id, used if name == NULL
*/
void CodecVideoOpen(VideoDecoder * decoder, const char *name, int codec_id)
{
AVCodec *video_codec;
Debug(3, "codec: using codec %s or ID %#04x\n", name, codec_id);
//
// ffmpeg compatibility hack
//
#if LIBAVCODEC_VERSION_INT <= AV_VERSION_INT(52,96,0)
if (name) {
if (!strcmp(name, "h264video_vdpau")) {
name = "h264_vdpau";
} else if (!strcmp(name, "mpeg4video_vdpau")) {
name = "mpeg4_vdpau";
} else if (!strcmp(name, "vc1video_vdpau")) {
name = "vc1_vdpau";
} else if (!strcmp(name, "wmv3video_vdpau")) {
name = "wmv3_vdpau";
}
}
#endif
if (name && (video_codec = avcodec_find_decoder_by_name(name))) {
Debug(3, "codec: vdpau decoder found\n");
} else if (!(video_codec = avcodec_find_decoder(codec_id))) {
Fatal(_("codec: codec ID %#04x not found\n"), codec_id);
}
decoder->VideoCodec = video_codec;
if (!(decoder->VideoCtx = avcodec_alloc_context3(video_codec))) {
Fatal(_("codec: can't allocate video codec context\n"));
}
// open codec
if (avcodec_open2(decoder->VideoCtx, video_codec, NULL) < 0) {
Fatal(_("codec: can't open video codec!\n"));
}
decoder->VideoCtx->opaque = decoder; // our structure
/*
// FIXME: the number of cpu's should be configurable
// Today this makes no big sense H264 is broken with current streams.
avcodec_thread_init(decoder->VideoCtx, 2); // support dual-cpu's
*/
Debug(3, "codec: video '%s'\n", decoder->VideoCtx->codec_name);
if (codec_id == CODEC_ID_H264) {
// 2.53 Ghz CPU is too slow for this codec at 1080i
//decoder->VideoCtx->skip_loop_filter = AVDISCARD_ALL;
//decoder->VideoCtx->skip_loop_filter = AVDISCARD_BIDIR;
}
if (video_codec->capabilities & CODEC_CAP_TRUNCATED) {
Debug(3, "codec: video can use truncated packets\n");
// we do not send complete frames
decoder->VideoCtx->flags |= CODEC_FLAG_TRUNCATED;
}
// FIXME: own memory management for video frames.
if (video_codec->capabilities & CODEC_CAP_DR1) {
Debug(3, "codec: can use own buffer management\n");
}
if (video_codec->capabilities & CODEC_CAP_HWACCEL_VDPAU) {
Debug(3, "codec: can export data for HW decoding (VDPAU)\n");
}
#ifdef CODEC_CAP_FRAME_THREADS
if (video_codec->capabilities & CODEC_CAP_FRAME_THREADS) {
Debug(3, "codec: codec supports frame threads\n");
}
#endif
//decoder->VideoCtx->debug = FF_DEBUG_STARTCODE;
if (video_codec->capabilities & CODEC_CAP_HWACCEL_VDPAU) {
// FIXME: get_format never called.
decoder->VideoCtx->get_format = Codec_get_format;
decoder->VideoCtx->get_buffer = Codec_get_buffer;
decoder->VideoCtx->release_buffer = Codec_release_buffer;
decoder->VideoCtx->reget_buffer = Codec_get_buffer;
//decoder->VideoCtx->draw_horiz_band = Codec_draw_horiz_band;
} else {
decoder->VideoCtx->hwaccel_context =
VideoGetVaapiContext(decoder->HwDecoder);
}
// our pixel format video hardware decoder hook
if (decoder->VideoCtx->hwaccel_context) {
decoder->VideoCtx->get_format = Codec_get_format;
decoder->VideoCtx->get_buffer = Codec_get_buffer;
decoder->VideoCtx->release_buffer = Codec_release_buffer;
decoder->VideoCtx->reget_buffer = Codec_get_buffer;
#if 0
decoder->VideoCtx->thread_count = 1;
decoder->VideoCtx->draw_horiz_band = NULL;
decoder->VideoCtx->slice_flags =
SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD;
//decoder->VideoCtx->flags |= CODEC_FLAG_EMU_EDGE;
#endif
}
//
// Prepare frame buffer for decoder
//
if (!(decoder->Frame = avcodec_alloc_frame())) {
Fatal(_("codec: can't allocate decoder frame\n"));
}
}
/**
** Close video decoder.
**
** @param video_decoder private video decoder
*/
void CodecVideoClose(VideoDecoder * video_decoder)
{
(void)video_decoder;
// FIXME: write close code
}
#if 0
#ifdef DEBUG
/**
** Display pts...
*/
void DisplayPts(AVCodecContext * video_ctx, AVFrame * frame)
{
int ms_delay;
if (frame->pts == (int64_t) AV_NOPTS_VALUE) {
printf("*");
}
ms_delay = (1000 * video_ctx->time_base.num) / video_ctx->time_base.den;
ms_delay += frame->repeat_pict * ms_delay / 2;
printf("codec: PTS %s%s %" PRId64 " %d/%d %dms\n",
frame->repeat_pict ? "r" : " ", frame->interlaced_frame ? "I" : " ",
frame->pts, video_ctx->time_base.num, video_ctx->time_base.den,
ms_delay);
}
#endif
#endif
/**
** Decode a video packet.
**
** @param decoder video decoder data
** @param avpkt video packet
**
** @note this version destroys avpkt!!
*/
void CodecVideoDecode(VideoDecoder * decoder, AVPacket * avpkt)
{
AVCodecContext *video_ctx;
AVFrame *frame;
int used;
int got_frame;
video_ctx = decoder->VideoCtx;
frame = decoder->Frame;
next_part:
// FIXME: this function can crash with bad packets
used = avcodec_decode_video2(video_ctx, frame, &got_frame, avpkt);
Debug(4, "%s: %p %d -> %d %d\n", __FUNCTION__, avpkt->data, avpkt->size,
used, got_frame);
if (got_frame) { // frame completed
//DisplayPts(video_ctx, frame);
if (video_ctx->hwaccel_context) {
VideoRenderFrame(decoder->HwDecoder, video_ctx, frame);
} else {
VideoRenderFrame(decoder->HwDecoder, video_ctx, frame);
}
} else {
// some frames are needed for references, interlaced frames ...
// could happen with h264 dvb streams, just drop data.
Debug(4, "codec: %8d incomplete interlaced frame %d bytes used\n",
video_ctx->frame_number, used);
}
if (used != avpkt->size) {
if (used == 0) {
goto next_part;
}
if (used >= 0) {
// some tv channels, produce this
Debug(4,
"codec: ooops didn't use complete video packet used %d of %d\n",
used, avpkt->size);
avpkt->data += used;
avpkt->size -= used;
goto next_part;
}
Debug(3, "codec: bad frame %d\n", used);
}
return;
}
//----------------------------------------------------------------------------
// Audio
//----------------------------------------------------------------------------
#if 0
///
/// Audio decoder typedef.
///
typedef struct _audio_decoder_ AudioDecoder;
#endif
///
/// Audio decoder structure.
///
struct _audio_decoder_
{
AVCodec *AudioCodec; ///< audio codec
AVCodecContext *AudioCtx; ///< audio codec context
/// audio parser to support wired dvb streaks
AVCodecParserContext *AudioParser;
int SampleRate; ///< old sample rate
int Channels; ///< old channels
};
/**
** Allocate a new audio decoder context.
**
** @param hw_decoder video hardware decoder
**
** @returns private decoder pointer for audio/video decoder.
*/
AudioDecoder *CodecAudioNewDecoder(void)
{
AudioDecoder *audio_decoder;
if (!(audio_decoder = calloc(1, sizeof(*audio_decoder)))) {
Fatal(_("codec: Can't allocate audio decoder\n"));
}
return audio_decoder;
}
/**
** Open audio decoder.
**
** @param audio_decoder private audio decoder
** @param name audio codec name
** @param codec_id audio codec id, used if name == NULL
*/
void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
int codec_id)
{
AVCodec *audio_codec;
if (name && (audio_codec = avcodec_find_decoder_by_name(name))) {
Debug(3, "codec: audio decoder '%s' found\n", name);
} else if (!(audio_codec = avcodec_find_decoder(codec_id))) {
Fatal(_("codec: codec ID %#04x not found\n"), codec_id);
// FIXME: errors aren't fatal
}
audio_decoder->AudioCodec = audio_codec;
if (!(audio_decoder->AudioCtx = avcodec_alloc_context3(audio_codec))) {
Fatal(_("codec: can't allocate audio codec context\n"));
}
// open codec
if (avcodec_open2(audio_decoder->AudioCtx, audio_codec, NULL) < 0) {
Fatal(_("codec: can't open audio codec\n"));
}
Debug(3, "codec: audio '%s'\n", audio_decoder->AudioCtx->codec_name);
if (audio_codec->capabilities & CODEC_CAP_TRUNCATED) {
Debug(3, "codec: audio can use truncated packets\n");
// we do not send complete frames
audio_decoder->AudioCtx->flags |= CODEC_FLAG_TRUNCATED;
}
if (!(audio_decoder->AudioParser =
av_parser_init(audio_decoder->AudioCtx->codec_id))) {
Fatal(_("codec: can't init audio parser\n"));
}
}
/**
** Close audio decoder.
**
** @param audio_decoder private audio decoder
*/
void CodecAudioClose(AudioDecoder * audio_decoder)
{
// FIXME: output any buffered data
if (audio_decoder->AudioParser) {
av_parser_close(audio_decoder->AudioParser);
audio_decoder->AudioParser = NULL;
}
if (audio_decoder->AudioCtx) {
avcodec_close(audio_decoder->AudioCtx);
av_freep(&audio_decoder->AudioCtx);
}
}
/**
** Decode an audio packet.
**
** PTS must be handled self.
**
** @param audio_decoder audio_Decoder data
** @param avpkt audio packet
*/
void CodecAudioDecode(AudioDecoder * audio_decoder, AVPacket * avpkt)
{
int16_t buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 4 +
FF_INPUT_BUFFER_PADDING_SIZE] __attribute__ ((aligned(16)));
AVCodecContext *audio_ctx;
int index;
AVPacket spkt[1];
if (!audio_decoder->AudioParser) {
Fatal(_("codec: internal error parser freeded while running\n"));
}
if (av_new_packet(spkt, avpkt->size + FF_INPUT_BUFFER_PADDING_SIZE)) {
Error(_("codec: out of memory\n"));
return;
}
memcpy(spkt->data, avpkt->data, avpkt->size);
memset(spkt->data + avpkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
audio_ctx = audio_decoder->AudioCtx;
index = 0;
while (avpkt->size > index) {
int n;
int l;
AVPacket dpkt[1];
av_init_packet(dpkt);
n = av_parser_parse2(audio_decoder->AudioParser, audio_ctx,
&dpkt->data, &dpkt->size, spkt->data + index, avpkt->size - index,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
if (dpkt->size) {
int buf_sz;
buf_sz = sizeof(buf);
l = avcodec_decode_audio3(audio_ctx, buf, &buf_sz, dpkt);
if (l < 0) { // no audio frame could be decompressed
Error(_("codec: error audio data\n"));
break;
}
#ifdef notyetFF_API_OLD_DECODE_AUDIO
// FIXME: ffmpeg git comeing
int got_frame;
avcodec_decode_audio4(audio_ctx, frame, &got_frame, dpkt);
#else
#endif
// FIXME: must first play remainings bytes, than change and play new.
if (audio_decoder->SampleRate != audio_ctx->sample_rate
|| audio_decoder->Channels != audio_ctx->channels) {
// FIXME: channels not support?
AudioSetup(audio_ctx->sample_rate, audio_ctx->channels);
audio_decoder->SampleRate = audio_ctx->sample_rate;
audio_decoder->Channels = audio_ctx->channels;
}
AudioEnqueue(buf, buf_sz);
if (dpkt->size > l) {
Error(_("codec: error more than one frame data\n"));
}
}
index += n;
}
av_destruct_packet(spkt);
}
//----------------------------------------------------------------------------
// Codec
//----------------------------------------------------------------------------
/**
** Codec init
*/
void CodecInit(void)
{
avcodec_register_all(); // register all formats and codecs
}
/**
** Codec exit.
*/
void CodecExit(void)
{
}

70
codec.h Normal file
View File

@ -0,0 +1,70 @@
///
/// @file codec.h @brief Codec module headerfile
///
/// Copyright (c) 2009 - 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup Codec
/// @{
//----------------------------------------------------------------------------
// Typedefs
//----------------------------------------------------------------------------
/// Video decoder typedef.
typedef struct _video_decoder_ VideoDecoder;
/// Audio decoder typedef.
typedef struct _audio_decoder_ AudioDecoder;
//----------------------------------------------------------------------------
// Prototypes
//----------------------------------------------------------------------------
/// Allocate a new video decoder context.
extern VideoDecoder *CodecVideoNewDecoder(VideoHwDecoder *);
/// Open video codec
extern void CodecVideoOpen(VideoDecoder *, const char *, int);
/// Close video codec
extern void CodecVideoClose(VideoDecoder *);
/// Decode a video packet
extern void CodecVideoDecode(VideoDecoder *, AVPacket * pkt);
/// Allocate a new audio decoder context.
extern AudioDecoder *CodecAudioNewDecoder(void);
/// Open audio codec
extern void CodecAudioOpen(AudioDecoder *, const char *, int);
/// Close audio codec
extern void CodecAudioClose(AudioDecoder *);
/// Decode an audio packet
extern void CodecAudioDecode(AudioDecoder *, AVPacket * pkt);
/// Setup and initialize codec module.
extern void CodecInit(void);
/// Cleanup and exit codec module.
extern void CodecExit(void);
/// @}

119
misc.h Normal file
View File

@ -0,0 +1,119 @@
///
/// @file misc.h @brief Misc function header file
///
/// Copyright (c) 2009 - 2011 by Lutz Sammer. All Rights Reserved.
///
/// Contributor(s):
/// Copied from uwm.
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup misc
/// @{
#include <syslog.h>
#include <stdarg.h>
#include <time.h> // clock_gettime
//////////////////////////////////////////////////////////////////////////////
// Defines
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Declares
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
// Variables
//////////////////////////////////////////////////////////////////////////////
extern int SysLogLevel; ///< how much information wanted
//////////////////////////////////////////////////////////////////////////////
// Prototypes
//////////////////////////////////////////////////////////////////////////////
static inline void Debug(const int, const char *format, ...)
__attribute__ ((format(printf, 2, 3)));
//////////////////////////////////////////////////////////////////////////////
// Inlines
//////////////////////////////////////////////////////////////////////////////
#define DebugLevel 4 /// private debug level
/**
** Debug output function.
**
** - 0 fatal errors and errors
** - 1 warnings
** - 2 info
** - 3 important debug and fixme's
*/
static inline void Debug(const int level, const char *format, ...)
{
if (SysLogLevel > level || DebugLevel > level) {
va_list ap;
va_start(ap, format);
vsyslog(LOG_ERR, format, ap);
va_end(ap);
}
}
/**
** Show error.
*/
#define Error(fmt...) Debug(0, fmt)
/**
** Show fatal error.
*/
#define Fatal(fmt...) do { Error(fmt); exit(-1); } while (0)
/**
** Show warning.
*/
#define Warning(fmt...) Debug(1, fmt)
/**
** Show info.
*/
#define Info(fmt...) Debug(2, fmt)
/**
** Get ticks in ms.
**
** @returns ticks in ms,
*/
static inline uint32_t GetMsTicks(void)
{
#ifdef CLOCK_MONOTONIC
struct timespec tspec;
clock_gettime(CLOCK_MONOTONIC, &tspec);
return (tspec.tv_sec * 1000) + (tspec.tv_nsec / (1000 * 1000));
#else
struct timeval tval;
if (gettimeofday(&tval, NULL) < 0) {
return 0;
}
return (tval.tv_sec * 1000) + (tval.tv_usec / 1000);
#endif
}
/// @}

328
ringbuffer.c Normal file
View File

@ -0,0 +1,328 @@
///
/// @file ringbuffer.c @brief Ringbuffer module
///
/// Copyright (c) 2009, 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
///
/// @defgroup Ringbuffer The ring buffer module.
///
/// Lock free ring buffer with only one writer and one reader.
///
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <alsa/iatomic.h>
#include "ringbuffer.h"
/// ring buffer structure
struct _ring_buffer_
{
char *Buffer; ///< ring buffer data
const char *BufferEnd; ///< end of buffer
size_t Size; ///< bytes in buffer (for faster calc)
const char *ReadPointer; ///< only used by reader
char *WritePointer; ///< only used by writer
/// The only thing modified by both
atomic_t Filled; ///< how many of the buffer is used
};
/**
** Allocate a new ring buffer.
**
** @param size Size of the ring buffer.
**
** @returns Allocated ring buffer, must be freed with
** RingBufferDel(), NULL for out of memory.
*/
RingBuffer *RingBufferNew(size_t size)
{
RingBuffer *rb;
if (!(rb = malloc(sizeof(*rb)))) { // allocate structure
return rb;
}
if (!(rb->Buffer = malloc(size))) { // allocate buffer
free(rb);
return NULL;
}
rb->Size = size;
rb->ReadPointer = rb->Buffer;
rb->WritePointer = rb->Buffer;
rb->BufferEnd = rb->Buffer + size;
atomic_set(&rb->Filled, 0);
return rb;
}
/**
** Free an allocated ring buffer.
*/
void RingBufferDel(RingBuffer * rb)
{
free(rb->Buffer);
free(rb);
}
/**
** Advance write pointer in ring buffer.
**
** @param rb Ring buffer to adance write pointer.
** @param cnt Number of bytes to be adavanced.
**
** @returns Number of bytes that could be advanced in ring buffer.
*/
size_t RingBufferWriteAdvance(RingBuffer * rb, size_t cnt)
{
size_t n;
n = rb->Size - atomic_read(&rb->Filled);
if (cnt > n) { // not enough space
cnt = n;
}
//
// Hitting end of buffer?
//
n = rb->BufferEnd - rb->WritePointer;
if (n > cnt) { // don't cross the end
rb->WritePointer += cnt;
} else { // reached or cross the end
rb->WritePointer = rb->Buffer;
if (n < cnt) {
n = cnt - n;
rb->WritePointer += n;
}
}
//
// Only atomic modification!
//
atomic_add(cnt, &rb->Filled);
return cnt;
}
/**
** Write to a ring buffer.
**
** @param rb Ring buffer to write to.
** @param buf Buffer of @p cnt bytes.
** @param cnt Number of bytes in buffer.
**
** @returns The number of bytes that could be placed in the ring
** buffer.
*/
size_t RingBufferWrite(RingBuffer * rb, const void *buf, size_t cnt)
{
size_t n;
n = rb->Size - atomic_read(&rb->Filled);
if (cnt > n) { // not enough space
cnt = n;
}
//
// Hitting end of buffer?
//
n = rb->BufferEnd - rb->WritePointer;
if (n > cnt) { // don't cross the end
memcpy(rb->WritePointer, buf, cnt);
rb->WritePointer += cnt;
} else { // reached or cross the end
memcpy(rb->WritePointer, buf, n);
rb->WritePointer = rb->Buffer;
if (n < cnt) {
buf += n;
n = cnt - n;
memcpy(rb->WritePointer, buf, n);
rb->WritePointer += n;
}
}
//
// Only atomic modification!
//
atomic_add(cnt, &rb->Filled);
return cnt;
}
/**
** Get write pointer and free bytes at this position of ring buffer.
**
** @param rb Ring buffer to write to.
** @param[out] wp Write pointer is placed here
**
** @returns The number of bytes that could be placed in the ring
** buffer at the write pointer.
*/
size_t RingBufferGetWritePointer(RingBuffer * rb, void **wp)
{
size_t n;
size_t cnt;
// Total free bytes available in ring buffer
cnt = rb->Size - atomic_read(&rb->Filled);
*wp = rb->WritePointer;
//
// Hitting end of buffer?
//
n = rb->BufferEnd - rb->WritePointer;
if (n <= cnt) { // reached or cross the end
return n;
}
return cnt;
}
/**
** Advance read pointer in ring buffer.
**
** @param rb Ring buffer to adance read pointer.
** @param cnt Number of bytes to be advanced.
**
** @returns Number of bytes that could be advanced in ring buffer.
*/
size_t RingBufferReadAdvance(RingBuffer * rb, size_t cnt)
{
size_t n;
n = atomic_read(&rb->Filled);
if (cnt > n) { // not enough filled
cnt = n;
}
//
// Hitting end of buffer?
//
n = rb->BufferEnd - rb->ReadPointer;
if (n > cnt) { // don't cross the end
rb->ReadPointer += cnt;
} else { // reached or cross the end
rb->ReadPointer = rb->Buffer;
if (n < cnt) {
n = cnt - n;
rb->ReadPointer += n;
}
}
//
// Only atomic modification!
//
atomic_sub(cnt, &rb->Filled);
return cnt;
}
/**
** Read from a ring buffer.
**
** @param rb Ring buffer to read from.
** @param buf Buffer of @p cnt bytes.
** @param cnt Number of bytes to be read.
**
** @returns Number of bytes that could be read from ring buffer.
*/
size_t RingBufferRead(RingBuffer * rb, void *buf, size_t cnt)
{
size_t n;
n = atomic_read(&rb->Filled);
if (cnt > n) { // not enough filled
cnt = n;
}
//
// Hitting end of buffer?
//
n = rb->BufferEnd - rb->ReadPointer;
if (n > cnt) { // don't cross the end
memcpy(buf, rb->ReadPointer, cnt);
rb->ReadPointer += cnt;
} else { // reached or cross the end
memcpy(buf, rb->ReadPointer, n);
rb->ReadPointer = rb->Buffer;
if (n < cnt) {
buf += n;
n = cnt - n;
memcpy(buf, rb->ReadPointer, n);
rb->ReadPointer += n;
}
}
//
// Only atomic modification!
//
atomic_sub(cnt, &rb->Filled);
return cnt;
}
/**
** Get read pointer and used bytes at this position of ring buffer.
**
** @param rb Ring buffer to read from.
** @param[out] rp Read pointer is placed here
**
** @returns The number of bytes that could be read from the ring
** buffer at the read pointer.
*/
size_t RingBufferGetReadPointer(RingBuffer * rb, const void **rp)
{
size_t n;
size_t cnt;
// Total used bytes in ring buffer
cnt = atomic_read(&rb->Filled);
*rp = rb->ReadPointer;
//
// Hitting end of buffer?
//
n = rb->BufferEnd - rb->ReadPointer;
if (n <= cnt) { // reached or cross the end
return n;
}
return cnt;
}
/**
** Get free bytes in ring buffer.
**
** @param rb Ring buffer.
**
** @returns Number of bytes free in buffer.
*/
size_t RingBufferFreeBytes(RingBuffer * rb)
{
return rb->Size - atomic_read(&rb->Filled);
}
/**
** Get used bytes in ring buffer.
**
** @param rb Ring buffer.
**
** @returns Number of bytes used in buffer.
*/
size_t RingBufferUsedBytes(RingBuffer * rb)
{
return atomic_read(&rb->Filled);
}

59
ringbuffer.h Normal file
View File

@ -0,0 +1,59 @@
///
/// @file ringbuffer.h @brief Ringbuffer module header file
///
/// Copyright (c) 2009, 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup Ringbuffer
/// @{
///< ring buffer typedef
typedef struct _ring_buffer_ RingBuffer;
///< create new ring buffer
extern RingBuffer *RingBufferNew(size_t);
///< free ring buffer
extern void RingBufferDel(RingBuffer *);
/// write into ring buffer
extern size_t RingBufferWrite(RingBuffer *, const void *, size_t);
/// get write pointer of ring buffer
extern size_t RingBufferGetWritePointer(RingBuffer *, void **);
/// advance write pointer of ring buffer
extern size_t RingBufferWriteAdvance(RingBuffer *, size_t);
/// read from ring buffer
extern size_t RingBufferRead(RingBuffer *, void *, size_t);
/// get read pointer of ring buffer
extern size_t RingBufferGetReadPointer(RingBuffer *, const void **);
/// advance read pointer of ring buffer
extern size_t RingBufferReadAdvance(RingBuffer *, size_t);
/// free bytes ring buffer
extern size_t RingBufferFreeBytes(RingBuffer *);
/// used bytes ring buffer
extern size_t RingBufferUsedBytes(RingBuffer *);
/// @}

808
softhddev.c Normal file
View File

@ -0,0 +1,808 @@
///
/// @file softhddev.c @brief A software HD device plugin for VDR.
///
/// Copyright (c) 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <libintl.h>
#define _(str) gettext(str) ///< gettext shortcut
#define _N(str) str ///< gettext_noop shortcut
#include <libavcodec/avcodec.h>
#include "misc.h"
#include "softhddev.h"
#include "audio.h"
#include "video.h"
#include "codec.h"
#define DEBUG
//////////////////////////////////////////////////////////////////////////////
// Audio
//////////////////////////////////////////////////////////////////////////////
static AudioDecoder *MyAudioDecoder; ///< audio decoder
static enum CodecID AudioCodecID; ///< current codec id
extern void AudioTest(void); // FIXME:
/**
** Play audio packet.
**
** @param data data of exactly one complete PES packet
** @param size size of PES packet
** @param id PES packet type
*/
void PlayAudio(const uint8_t * data, int size, uint8_t id)
{
int n;
AVPacket avpkt[1];
// PES header 0x00 0x00 0x01 ID
// ID 0xBD 0xC0-0xCF
// channel switch: SetAudioChannelDevice: SetDigitalAudioDevice:
// Detect audio code
// MPEG-PS mp2 MPEG1, MPEG2, AC3
if (size < 9) {
Error("[softhddev] invalid audio packet\n");
return;
}
avpkt->pts = AV_NOPTS_VALUE;
avpkt->dts = AV_NOPTS_VALUE;
if (data[7] & 0x80) {
avpkt->pts =
(int64_t) (data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] &
0xFE) << 15 | data[12] << 7 | (data[13] & 0xFE) >> 1;
// Debug(3, "audio: pts %ld\n", avpkt->pts);
}
if (data[7] & 0x40) {
avpkt->dts =
(int64_t) (data[14] & 0x0E) << 29 | data[15] << 22 | (data[16] &
0xFE) << 15 | data[17] << 7 | (data[18] & 0xFE) >> 1;
Debug(3, "audio: dts %ld\n", avpkt->dts);
}
n = data[8]; // header size
data += 9 + n;
size -= 9 + n; // skip pes header
if (size <= 0) {
Error("[softhddev] invalid audio packet\n");
return;
}
// Syncword - 0x0B77
if (data[0] == 0x0B && data[1] == 0x77) {
if (!MyAudioDecoder) {
MyAudioDecoder = CodecAudioNewDecoder();
AudioCodecID = CODEC_ID_NONE;
}
if (AudioCodecID != CODEC_ID_AC3) {
Debug(3, "[softhddev]%s: AC-3 %d\n", __FUNCTION__, id);
CodecAudioClose(MyAudioDecoder);
CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_AC3);
AudioCodecID = CODEC_ID_AC3;
}
// Syncword - 0xFFFC - 0xFFFF
} else if (data[0] == 0xFF && (data[1] & 0xFC) == 0xFC) {
if (!MyAudioDecoder) {
MyAudioDecoder = CodecAudioNewDecoder();
AudioCodecID = CODEC_ID_NONE;
}
if (AudioCodecID != CODEC_ID_MP2) {
Debug(3, "[softhddev]%s: MP2 %d\n", __FUNCTION__, id);
CodecAudioClose(MyAudioDecoder);
CodecAudioOpen(MyAudioDecoder, NULL, CODEC_ID_MP2);
AudioCodecID = CODEC_ID_MP2;
}
} else {
// no start package
// FIXME: Nick/Viva sends this shit, need to find sync in packet
// FIXME: otherwise it takes too long until sound appears
if (AudioCodecID == CODEC_ID_NONE) {
Debug(3, "[softhddev]%s: ??? %d\n", __FUNCTION__, id);
return;
}
}
// no decoder or codec known
if (!MyAudioDecoder || AudioCodecID == CODEC_ID_NONE) {
return;
}
av_init_packet(avpkt);
avpkt->data = (void *)data;
avpkt->size = size;
//memset(avpkt->data + avpkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
CodecAudioDecode(MyAudioDecoder, avpkt);
}
/**
** Mute audio device.
*/
void Mute(void)
{
AudioSetVolume(0);
}
/**
** Set volume of audio device.
**
** @param volume VDR volume (0 .. 255)
*/
void SetVolumeDevice(int volume)
{
AudioSetVolume((volume * 100) / 255);
}
//////////////////////////////////////////////////////////////////////////////
// Video
//////////////////////////////////////////////////////////////////////////////
#include <alsa/iatomic.h> // portable atomic_t
uint32_t VideoSwitch;
static int NewVideoStream; ///< new video stream
static VideoDecoder *MyVideoDecoder; ///< video decoder
static enum CodecID VideoCodecID; ///< current codec id
static const char *X11DisplayName; ///< x11 display name
static volatile int Usr1Signal; ///< true got usr1 signal
/// video PES buffer default size
#define VIDEO_BUFFER_SIZE (512 * 1024)
#define VIDEO_PACKET_MAX 128 ///< max number of video packets
/// video PES packet ring buffer
static AVPacket VideoPacketRb[VIDEO_PACKET_MAX];
static int VideoPacketWrite; ///< write pointer
static int VideoPacketRead; ///< read pointer
static atomic_t VideoPacketsFilled; ///< how many of the buffer is used
static int VideoMaxPacketSize; ///< biggest used packet buffer
static uint32_t VideoStartTick; ///< video start tick
extern void VideoWakeup(void); ///< wakeup video handler
/**
** Initialize video packet ringbuffer.
*/
static void VideoPacketInit(void)
{
int i;
AVPacket *avpkt;
Debug(4, "[softhddev]: %s\n", __FUNCTION__);
for (i = 0; i < VIDEO_PACKET_MAX; ++i) {
avpkt = &VideoPacketRb[i];
// build a clean ffmpeg av packet
av_init_packet(avpkt);
avpkt->destruct = av_destruct_packet;
avpkt->data = av_malloc(VIDEO_BUFFER_SIZE);
if (!avpkt->data) {
Fatal(_("[softhddev]: out of memory\n"));
}
avpkt->size = VIDEO_BUFFER_SIZE;
avpkt->priv = NULL;
}
atomic_set(&VideoPacketsFilled, 0);
}
/**
** Place video data in packet ringbuffer.
*/
static void VideoEnqueue(const void *data, int size)
{
AVPacket *avpkt;
// Debug(3, "video: enqueue %d\n", size);
avpkt = &VideoPacketRb[VideoPacketWrite];
if (avpkt->stream_index + size + FF_INPUT_BUFFER_PADDING_SIZE >=
avpkt->size) {
Warning(_("video: packet buffer too small for %d\n"),
avpkt->stream_index + size + FF_INPUT_BUFFER_PADDING_SIZE);
av_grow_packet(avpkt,
(size + FF_INPUT_BUFFER_PADDING_SIZE + VIDEO_BUFFER_SIZE / 2)
/ (VIDEO_BUFFER_SIZE / 2));
}
#ifdef DEBUG
if (!avpkt->stream_index) { // debug save time of first packet
avpkt->dts = GetMsTicks();
}
#endif
if (!VideoStartTick) { // tick of first valid packet
VideoStartTick = GetMsTicks();
}
memcpy(avpkt->data + avpkt->stream_index, data, size);
avpkt->stream_index += size;
if (avpkt->stream_index > VideoMaxPacketSize) {
VideoMaxPacketSize = avpkt->stream_index;
Debug(3, "video: max used PES packet size: %d\n", VideoMaxPacketSize);
}
}
/**
** Finish current packet advance to next.
*/
static void VideoNextPacket(int codec_id)
{
AVPacket *avpkt;
avpkt = &VideoPacketRb[VideoPacketWrite];
if (!avpkt->stream_index) { // ignore empty packets
if (codec_id == CODEC_ID_NONE) {
Debug(3, "video: possible stream change loss\n");
}
return;
}
if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX - 1) {
// no free slot available drop last packet
Error(_("video: no empty slot in packet ringbuffer\n"));
avpkt->stream_index = 0;
if (codec_id == CODEC_ID_NONE) {
Debug(3, "video: possible stream change loss\n");
}
return;
}
// clear area for decoder, always enough space allocated
memset(avpkt->data + avpkt->stream_index, 0, FF_INPUT_BUFFER_PADDING_SIZE);
avpkt->priv = (void *)(size_t) codec_id;
// advance packet write
VideoPacketWrite = (VideoPacketWrite + 1) % VIDEO_PACKET_MAX;
atomic_inc(&VideoPacketsFilled);
// intialize next package to use
avpkt = &VideoPacketRb[VideoPacketWrite];
avpkt->stream_index = 0;
avpkt->pts = AV_NOPTS_VALUE;
avpkt->dts = AV_NOPTS_VALUE;
VideoWakeup();
}
/**
** Decode from PES packet ringbuffer.
*/
int VideoDecode(void)
{
int filled;
AVPacket *avpkt;
int saved_size;
static int last_codec_id = CODEC_ID_NONE;
filled = atomic_read(&VideoPacketsFilled);
//Debug(3, "video: decode %3d packets buffered\n", filled);
if (!filled) {
// Debug(3, "video: decode no packets buffered\n");
return -1;
}
avpkt = &VideoPacketRb[VideoPacketRead];
//
// handle queued commands
//
switch ((int)(size_t) avpkt->priv) {
case CODEC_ID_NONE:
if (last_codec_id != CODEC_ID_NONE) {
last_codec_id = CODEC_ID_NONE;
CodecVideoClose(MyVideoDecoder);
goto skip;
}
break;
case CODEC_ID_MPEG2VIDEO:
if (last_codec_id != CODEC_ID_MPEG2VIDEO) {
last_codec_id = CODEC_ID_MPEG2VIDEO;
CodecVideoOpen(MyVideoDecoder, 0 ? "mpegvideo_vdpau" : NULL,
CODEC_ID_MPEG2VIDEO);
}
break;
case CODEC_ID_H264:
if (last_codec_id != CODEC_ID_H264) {
last_codec_id = CODEC_ID_H264;
CodecVideoOpen(MyVideoDecoder, 0 ? "h264video_vdpau" : NULL,
CODEC_ID_H264);
}
break;
default:
break;
}
// avcodec_decode_video2 needs size
saved_size = avpkt->size;
avpkt->size = avpkt->stream_index;
avpkt->stream_index = 0;
CodecVideoDecode(MyVideoDecoder, avpkt);
avpkt->size = saved_size;
skip:
// advance packet read
VideoPacketRead = (VideoPacketRead + 1) % VIDEO_PACKET_MAX;
atomic_dec(&VideoPacketsFilled);
return 0;
}
/**
** Flush video buffer.
*/
void VideoFlushInput(void)
{
// flush all buffered packets
while (atomic_read(&VideoPacketsFilled)) {
VideoPacketRead = (VideoPacketRead + 1) % VIDEO_PACKET_MAX;
atomic_dec(&VideoPacketsFilled);
}
VideoStartTick = 0;
}
/**
** Wakeup video handler.
*/
void VideoWakeup(void)
{
int filled;
uint32_t now;
uint64_t delay;
VideoDisplayHandler();
return;
filled = atomic_read(&VideoPacketsFilled);
if (!filled) {
Debug(3, "video: wakeup no packets buffered\n");
return;
}
now = GetMsTicks();
if (filled < VIDEO_PACKET_MAX && VideoStartTick + 1000 > now) {
delay = AudioGetDelay() / 90;
if (delay < 100) { // no audio delay known
delay = 750;
}
delay -= 40;
if (VideoStartTick + delay > now) {
Debug(3, "video: %d packets %u/%lu delayed\n", filled,
(unsigned)(now - VideoStartTick), delay);
return;
}
}
VideoDecode();
#if 0
AVPacket *avpkt;
while (filled) {
avpkt = &VideoPacketRb[VideoPacketRead];
now = GetMsTicks();
if (avpkt->dts + 500 > now) {
Debug(3, "video: %d packets %u delayed\n", filled,
(unsigned)(now - avpkt->dts));
return;
}
filled = atomic_read(&VideoPacketsFilled);
}
#endif
}
/**
** Try video start.
**
** Could be called, when already started.
*/
static void StartVideo(void)
{
VideoInit(X11DisplayName);
VideoOsdInit();
if (!MyVideoDecoder) {
VideoHwDecoder *hw_decoder;
if ((hw_decoder = VideoNewHwDecoder())) {
MyVideoDecoder = CodecVideoNewDecoder(hw_decoder);
VideoCodecID = CODEC_ID_NONE;
}
}
VideoPacketInit();
}
/**
** Play video packet.
**
** @param data data of exactly one complete PES packet
** @param size size of PES packet
**
** @note vdr sends incomplete packets, va-api h264 decoder only
** supports complete packets.
** We buffer here until we receive an complete PES Packet, which
** is no problem, the audio is always far behind us.
*/
void PlayVideo(const uint8_t * data, int size)
{
const uint8_t *check;
uint64_t pts;
int n;
if (Usr1Signal) { // x11 server ready
Usr1Signal = 0;
StartVideo();
}
if (!MyVideoDecoder) { // no x11 video started
return;
}
if (NewVideoStream) {
Debug(3, "video: new stream %d\n", GetMsTicks() - VideoSwitch);
VideoNextPacket(CODEC_ID_NONE);
VideoCodecID = CODEC_ID_NONE;
NewVideoStream = 0;
}
// must be a PES start code
if (data[0] || data[1] || data[2] != 0x01 || size < 9) {
Error(_("[softhddev] invalid PES video packet\n"));
return;
}
n = data[8]; // header size
// wrong size
if (size < 9 + n) {
Error(_("[softhddev] invalid video packet\n"));
return;
}
check = data + 9 + n;
// FIXME: get pts/dts, when we need it
pts = AV_NOPTS_VALUE;
if (data[7] & 0x80) {
pts =
(int64_t) (data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] &
0xFE) << 15 | data[12] << 7 | (data[13] & 0xFE) >> 1;
// Debug(3, "video: pts %ld\n", pts);
}
// FIXME: no valid mpeg2/h264 detection yet
if (0) {
printf("%02x: %02x %02x %02x %02x %02x\n", data[6], check[0], check[1],
check[2], check[3], check[4]);
}
// PES_VIDEO_STREAM 0xE0 or PES start code
if ((data[6] & 0xC0) != 0x80 || (!check[0] && !check[1]
&& check[2] == 0x1)) {
if (VideoCodecID == CODEC_ID_MPEG2VIDEO) {
VideoNextPacket(CODEC_ID_MPEG2VIDEO);
} else {
VideoCodecID = CODEC_ID_MPEG2VIDEO;
}
// Access Unit Delimiter
} else if (!check[0] && !check[1] && !check[2] && check[3] == 0x1
&& check[4] == 0x09) {
if (VideoCodecID == CODEC_ID_H264) {
VideoNextPacket(CODEC_ID_H264);
} else {
Debug(3, "video: h264 detected\n");
VideoCodecID = CODEC_ID_H264;
}
} else {
// this happens when vdr sends incomplete packets
if (VideoCodecID == CODEC_ID_NONE) {
Debug(3, "video: not detected\n");
return;
}
if (VideoCodecID == CODEC_ID_MPEG2VIDEO) {
// mpeg codec supports incomplete packages
VideoNextPacket(CODEC_ID_MPEG2VIDEO);
}
}
// SKIP PES header
size -= 9 + n;
VideoEnqueue(check, size);
}
//////////////////////////////////////////////////////////////////////////////
/**
** Set play mode, called on channel switch.
*/
void SetPlayMode(void)
{
if (MyVideoDecoder) {
if (VideoCodecID != CODEC_ID_NONE) {
NewVideoStream = 1;
VideoSwitch = GetMsTicks();
}
}
if (MyAudioDecoder) {
// FIXME: does this clear the audio ringbuffer?
CodecAudioClose(MyAudioDecoder);
AudioCodecID = CODEC_ID_NONE;
}
}
//////////////////////////////////////////////////////////////////////////////
// OSD
//////////////////////////////////////////////////////////////////////////////
/**
** Get OSD size and aspect.
*/
void GetOsdSize(int *width, int *height, double *aspect)
{
static char done;
// FIXME: should be configured!
*width = 1920;
*height = 1080;
//*width = 768;
//*height = 576;
*aspect = 16.0 / 9.0 / (double)*width * (double)*height;
if (!done) {
Debug(3, "[softhddev]%s: %dx%d %g\n", __FUNCTION__, *width, *height,
*aspect);
done = 1;
}
}
/**
** Close OSD.
*/
void OsdClose(void)
{
VideoOsdClear();
}
/**
** Draw an OSD pixmap.
*/
void OsdDrawARGB(int x, int y, int height, int width, const uint8_t * argb)
{
VideoOsdDrawARGB(x, y, height, width, argb);
}
//////////////////////////////////////////////////////////////////////////////
static char StartX11Server; ///< flag start the x11 server
/**
** Return command line help string.
*/
const char *CommandLineHelp(void)
{
return " -a device\talsa audio device (fe. hw:0,0)\n"
" -d display\tdisplay of x11 server (f.e :0.0)\n"
" -g geometry\tx11 window geometry wxh+x+y\n"
" -x\tstart x11 server\n";
}
/**
** Process the command line arguments.
**
** @param argc number of arguments
** @param argv arguments vector
*/
int ProcessArgs(int argc, char *const argv[])
{
//
// Parse arguments.
//
for (;;) {
switch (getopt(argc, argv, "-a:d:g:x")) {
case 'a': // audio device
AudioSetDevice(optarg);
continue;
case 'd': // x11 display name
X11DisplayName = optarg;
continue;
case 'g': // geometry
if (VideoSetGeometry(optarg) < 0) {
fprintf(stderr,
_
("Bad formated geometry please use: [=][<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>]\n"));
return 0;
}
continue;
case 'x': // x11 server
StartX11Server = 1;
continue;
case EOF:
break;
case '-':
fprintf(stderr, _("We need no long options\n"));
return 0;
case ':':
fprintf(stderr, _("Missing argument for option '%c'\n"),
optopt);
return 0;
default:
fprintf(stderr, _("Unkown option '%c'\n"), optopt);
return 0;
}
break;
}
while (optind < argc) {
fprintf(stderr, _("Unhandled argument '%s'\n"), argv[optind++]);
}
return 1;
}
//////////////////////////////////////////////////////////////////////////////
// Init/Exit
//////////////////////////////////////////////////////////////////////////////
#include <sys/types.h>
#include <sys/wait.h>
#define XSERVER_MAX_ARGS 512 ///< how many arguments support
static const char *X11Server = "/usr/bin/X"; ///< default x11 server
static const char *X11ServerArguments; ///< default command arguments
static pid_t X11ServerPid; ///< x11 server pid
/**
** USR1 signal handler.
**
** @param sig signal number
*/
static void Usr1Handler(int __attribute__ ((unused)) sig)
{
++Usr1Signal;
Debug(3, "x-setup: got signal usr1\n");
}
/**
** Start the X server
*/
static void StartXServer(void)
{
struct sigaction usr1;
pid_t pid;
const char *sval;
const char *args[XSERVER_MAX_ARGS];
int argn;
char *buf;
// X server
if (X11Server) {
args[0] = X11Server;
} else {
Error(_("x-setup: No X server configured!\n"));
return;
}
argn = 1;
if (X11DisplayName) { // append display name
args[argn++] = X11DisplayName;
}
// split X server arguments string into words
if ((sval = X11ServerArguments)) {
char *s;
s = buf = strdupa(sval);
while ((sval = strsep(&s, " \t"))) {
args[argn++] = sval;
if (argn == XSERVER_MAX_ARGS - 1) { // argument overflow
Error(_("x-setup: too many arguments for xserver\n"));
// argn = 1;
break;
}
}
}
// FIXME: auth
// FIXME: append VTxx
args[argn] = NULL;
// arm the signal
memset(&usr1, 0, sizeof(struct sigaction));
usr1.sa_handler = Usr1Handler;
sigaction(SIGUSR1, &usr1, NULL);
Debug(3, "x-setup: Starting X server '%s' '%s'\n", args[0],
X11ServerArguments);
// fork
if ((pid = vfork())) { // parent
X11ServerPid = pid;
Debug(3, "x-setup: Started x-server pid=%d\n", X11ServerPid);
return;
}
// child
signal(SIGUSR1, SIG_IGN); // ignore to force answer
// start the X server
execvp(args[0], (char *const *)args);
Error(_("x-setup: Failed to start X server '%s'\n"), args[0]);
}
/**
** Prepare plugin.
*/
void Start(void)
{
if (StartX11Server) {
StartXServer();
}
CodecInit();
// FIXME: AudioInit for HDMI after X11 startup
AudioInit();
if (!StartX11Server) {
StartVideo();
}
}
/**
** Stop plugin.
*/
void Stop(void)
{
Debug(3, "video: max used PES packet size: %d\n", VideoMaxPacketSize);
// lets hope that vdr does a good thead cleanup
if (MyVideoDecoder) {
CodecVideoClose(MyVideoDecoder);
MyVideoDecoder = NULL;
}
if (MyAudioDecoder) {
CodecAudioClose(MyAudioDecoder);
MyAudioDecoder = NULL;
}
VideoExit();
AudioExit();
CodecExit();
if (StartX11Server) {
Debug(3, "x-setup: Stop x11 server\n");
if (X11ServerPid) {
kill(X11ServerPid, SIGTERM);
}
}
}
/**
** Main thread hook, periodic called from main thread.
*/
void MainThreadHook(void)
{
VideoDisplayHandler();
}

67
softhddev.h Normal file
View File

@ -0,0 +1,67 @@
///
/// @file softhddev.h @brief software HD device plugin header file.
///
/// Copyright (c) 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
#ifdef __cplusplus
extern "C"
{
#endif
/// C callback feed key press
extern void FeedKeyPress(const char *, const char *, int, int);
/// C plugin get osd size and ascpect
extern void GetOsdSize(int *, int *, double *);
/// C plugin close osd
extern void OsdClose(void);
/// C plugin draw osd pixmap
extern void OsdDrawARGB(int, int, int, int, const uint8_t *);
/// C plugin play audio packet
extern void PlayAudio(const uint8_t *, int, uint8_t);
/// C plugin mute audio
extern void Mute(void);
/// C plugin set audio volume
extern void SetVolumeDevice(int);
/// C plugin play video packet
extern void PlayVideo(const uint8_t *, int);
/// C plugin play TS video packet
extern void PlayTsVideo(const uint8_t *, int);
/// C plugin set play mode
extern void SetPlayMode(void);
/// C plugin command line help
extern const char *CommandLineHelp(void);
/// C plugin process the command line arguments
extern int ProcessArgs(int, char *const[]);
/// C plugin start code
extern void Start(void);
/// C plugin stop code
extern void Stop(void);
/// C plugin main thread hook
extern void MainThreadHook(void);
#ifdef __cplusplus
}
#endif

3827
video.c Normal file

File diff suppressed because it is too large Load Diff

89
video.h Normal file
View File

@ -0,0 +1,89 @@
///
/// @file video.h @brief Video module header file
///
/// Copyright (c) 2009 - 2011 by Johns. All Rights Reserved.
///
/// Contributor(s):
///
/// License: AGPLv3
///
/// This program is free software: you can redistribute it and/or modify
/// it under the terms of the GNU Affero General Public License as
/// published by the Free Software Foundation, either version 3 of the
/// License.
///
/// 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 Affero General Public License for more details.
///
/// $Id$
//////////////////////////////////////////////////////////////////////////////
/// @addtogroup Video
/// @{
//----------------------------------------------------------------------------
// Typedefs
//----------------------------------------------------------------------------
/// Video hardware decoder typedef
typedef struct _video_hw_decoder_ VideoHwDecoder;
//----------------------------------------------------------------------------
// Variables
//----------------------------------------------------------------------------
//extern unsigned VideoWindowWidth; ///< current video output width
//extern unsigned VideoWindowHeight; ///< current video output height
//----------------------------------------------------------------------------
// Prototypes
//----------------------------------------------------------------------------
/// Allocate new video hardware decoder.
extern VideoHwDecoder *VideoNewHwDecoder(void);
/// Get and allocate a video hardware surface.
extern unsigned VideoGetSurface(VideoHwDecoder *);
/// Release a video hardware surface.
extern void VideoReleaseSurface(VideoHwDecoder *, unsigned);
#ifdef LIBAVCODEC_VERSION
/// Render a ffmpeg frame
extern void VideoRenderFrame(VideoHwDecoder *, AVCodecContext *, AVFrame *);
/// Get ffmpeg vaapi context
extern struct vaapi_context *VideoGetVaapiContext(VideoHwDecoder *);
/// Callback to negotiate the PixelFormat.
extern enum PixelFormat Video_get_format(VideoHwDecoder *, AVCodecContext *,
const enum PixelFormat *);
#endif
/// Display video TEST
extern void VideoDisplayHandler(void);
/// set video mode
//extern void VideoSetVideoMode(int, int, int, int);
/// set video geometry
extern int VideoSetGeometry(const char *);
/// Clear OSD
extern void VideoOsdClear(void);
/// Draw an OSD ARGB image
extern void VideoOsdDrawARGB(int, int, int, int, const uint8_t *);
extern void VideoOsdInit(void); ///< setup osd
extern void VideoOsdExit(void); ///< cleanup osd
extern void VideoInit(const char *); ///< setup video module
extern void VideoExit(void); ///< cleanup and exit video module
extern void VideoFlushInput(void); ///< flush codec input buffers
extern int VideoDecode(void); ///< decode
/// @}