mirror of
https://projects.vdr-developer.org/git/vdr-plugin-softhddevice.git
synced 2023-10-10 19:16:51 +02:00
C part of the plugin.
This commit is contained in:
parent
ab6c3b4de8
commit
ce97b938ca
930
audio.c
Normal file
930
audio.c
Normal 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
47
audio.h
Normal 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
627
codec.c
Normal 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
70
codec.h
Normal 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
119
misc.h
Normal 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
328
ringbuffer.c
Normal 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
59
ringbuffer.h
Normal 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
808
softhddev.c
Normal 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
67
softhddev.h
Normal 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
|
89
video.h
Normal file
89
video.h
Normal 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
|
||||||
|
|
||||||
|
/// @}
|
Loading…
Reference in New Issue
Block a user