Logo Search packages:      
Sourcecode: vdr-plugin-bitstreamout version File versions  Download package

replay.c

/*
 * replay.c:      Forward AC3 stream to the S/P-DIF interface of
 *          sound cards supported by ALSA. (NO decoding!)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
 *
 * Copyright (C) 2002-2005 Werner Fink, <werner@suse.de>
 * Copyright (C) 2003 Sven Goethel, <sven@jausoft.com>
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include "types.h"
#include "bytes.h"
#include "replay.h"
#include "channel.h"
#include "ac3.h"
#include "dts.h"
#include "lpcm.h"
#include "mp2.h"

// --- cReplayOutSPDif : Forward AC3 stream to S/P-DIF of a sound card with ALSA -----------

const uint_32 cReplayOutSPDif::PS1magic = 0x000001bd;
const uint_16 cReplayOutSPDif::AC3magic = 0x0b77;
const uint_32 cReplayOutSPDif::DTSmagic = 0x7ffe8001;
uint_8  * cReplayOutSPDif::pesdata;
cBounce * cReplayOutSPDif::bounce;

cReplayOutSPDif::cReplayOutSPDif(spdif &dev, ctrl_t &up, cBounce * bPtr, const char *script)
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10300
:cAudio(), cThread("bso(replay): Forwarding bitstream"),
 ctr(), wait(), SPDIFmute(script), spdifDev(&dev), setup(up)
#else
:cAudio(), cThread(), ctr(), wait(), SPDIFmute(script), spdifDev(&dev), setup(up)
#endif
{
    flags = 0;
    ctr.Lock();
    stream = NULL;
    ctr.Unlock();
    bounce = bPtr;
    bounce->bank(0);
    pesdata = setup.buf + TRANSFER_START;
}

cReplayOutSPDif::~cReplayOutSPDif(void)
{
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10320
    if (test_flag(ACTIVE))
#endif
    Clear();
}

void cReplayOutSPDif::Activate(bool onoff)
{
    if (test_setup(CLEAR))
      onoff = false;

    if (onoff) {
      if (*spdifDev) {
          debug("Activated failed due S/P-DIF already open\n");
          goto out;                       // An other has opened the S/P-DIF
      }
      if (test_setup(RESET)) {
          clear_flag(FAILED);
          clear_setup(RESET);
      }
      if (test_flag(FAILED) || test_flag(ACTIVE)) {
          debug("Activated failed due set %s\n", test_flag(FAILED) ? "FAILED" : "ACTIVE");
          goto out;
      }
      set_flag(ACTIVE);
      bounce->flush();
      Start();                      // Warning: Start() sleeps 10ms after work
      debug("Activated\n");
    } else {
      int n = 50;                   // 500 ms

      clear_flag(ACTIVE);                 // Set only above
      do {
          bounce->flush();
          bounce->signal();
          wait.msec(10);                  // More than 2ms
          if (!Active())
            break;
      } while (test_flag(RUNNING) && (n-- > 0));
      Cancel(1);

      if (test_flag(RUNNING) || Active()) {
          esyslog("REPLAY: Forwarding bitstream thread was broken");
          wait.msec(10);                  // More than 2ms
          ctr.Lock();
          iec60958* curr = stream;
          ctr.Unlock();
          if (curr) {                     // thread broken
            spdifDev->Close();
            clear_flag(BOUNDARY);
            curr->Reset();
            bounce->leaveio();
            }
          clear_flag(RUNNING);            // Should not happen
      }

      ctr.Lock();
      stream = NULL;
      ctr.Unlock();
      debug("DeActivated\n");
    }
out:
    return;
}

void cReplayOutSPDif::Action(void)
{
    set_flag(RUNNING);
    ctr.Lock();
    iec60958 *curr = stream;
    ctr.Unlock();

    realtime("REPLAY: ");
    if (!curr) {
      esyslog("REPLAY: no stream for spdif interface");
      set_flag(FAILED);
      goto out;
    } else {
        if (!spdifDev->Open(curr, this)) {
          esyslog("REPLAY: can't open spdif interface");
          set_flag(FAILED);
          goto out;
      }
    }

#if defined(VDRVERSNUM) && VDRVERSNUM < 10300
    dsyslog("REPLAY: Forwarding bitstream thread started (pid=%d)", getpid());
#endif
    bounce->takeio();
    while (test_flag(ACTIVE)) {
      ssize_t len;

      if (test_setup(MUTE)) {
          if (!test_flag(WASMUTED)) {
            spdifDev->Clear();
            clear_flag(BOUNDARY);
            bounce->flush();
            curr->Clear();
          }
          set_flag(WASMUTED);
      }

      if (!spdifDev->Synchronize(bounce)) // Wait on bounce->signal()
          continue;                       // No break on large delay gaps

      if (!test_flag(ACTIVE))
          break;

      if (test_setup(MUTE)) {
          if (!test_flag(WASMUTED)) {
            spdifDev->Clear();
            clear_flag(BOUNDARY);
            bounce->flush();
            curr->Clear();
          }
          set_flag(WASMUTED);
          continue;
      }

      if ((len = bounce->fetch(pesdata, spdifDev->Available(TRANSFER_MEM))))
          spdifDev->Forward(pesdata, len, bounce);

    }
    spdifDev->Close(this);
    clear_flag(BOUNDARY);
    curr->Reset();
#if defined(VDRVERSNUM) && VDRVERSNUM < 10300
    dsyslog("REPLAY: Forwarding bitstream thread ended (pid=%d)", getpid());
#endif
out:
    ctr.Lock();
    stream = NULL;
    ctr.Unlock();
    bounce->flush();
    bounce->leaveio();
    clear_flag(RUNNING);
}

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
inline bool cReplayOutSPDif::OffsetToDvdOfPS1(const uchar *const b, int &off, const int cnt, const uchar id)
#else
inline bool cReplayOutSPDif::OffsetToDvdOfPS1(const uchar *const b, int &off, const int cnt)
#endif
{
    const off_t rest = cnt-off;
    bool ret = false;                     // DVB streams do not have sub stream headers
    cHandle dvd(&b[off], rest);
    ctr.Lock();
    iec60958* curr = stream;
    ctr.Unlock();

    TEST(dvd >= (size_t)4) {
      uint_32 ul = dvd;
      uint_8  ub = (uint_8)(ul>>24);
      uint_8  tr = (ub & 0x1F) + 0x21;
      off_t   o  = (off_t) (ul & 0x0000ffff) + 3;

      unsigned int sample_rate;
      uint_8 m;

      switch (ub) {
      case 0x80 ... 0x87:                 // AC3 from e.g. DVD
          if (curr != &ac3 || curr->track != tr)
            break;
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (id != ub)
            break;
#endif
          ret = true;                     // found, success
          off += 4;
          break;
      case 0x88 ... 0x8f:                 // DTS
          if (curr != &dts || curr->track != tr)
            break;
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (id != ub)
            break;
#endif
          ret = true;                     // found, success
          off += 4;
          break;
      case 0xa0 ... 0xa7:                 // Linear PCM Audio
          if (o == 3) {             // broken stream
            dvd++;
            uint_32 ui = dvd;
            dvd += 4;
            m = dvd;
            if ((ui == 0) && (m == 0)) {
                off = cnt;
                break;              // Don't support broken streams (dvd plugin).
            }
          } else {
            dvd += 5;
            m = dvd;
          }
          if (curr != &pcm || curr->track != tr)
            break;
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (id != ub)
            break;
#endif
          switch (m & 0x30) {
          case 0x00: sample_rate = 48000;
            break;
          case 0x20: sample_rate = 44100;
            break;
          case 0x30: sample_rate = 32000;
            break;
          case 0x10:                      // 96kHz  currently not supported
          default:   sample_rate = 0;
            break;
          }
          if (curr->sample_rate != sample_rate)
            break;
          if (rest < 7)
            break;
          ret = true;                     // found, success
          off += 7;
          break;
      default:                      // Should not happen, e.g. stream
          break;                    // is broken. Should we reset???
      }
    } END (dvd);

    return ret;
}

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
inline bool cReplayOutSPDif::ScanPayOfPS1(const uchar *const b, int &off, const int cnt, const uchar id)
#else
inline bool cReplayOutSPDif::ScanPayOfPS1(const uchar *const b, int &off, const int cnt)
#endif
{
    const off_t rest = cnt-off;
    cHandle dvb(&b[off], rest);
    ctr.Lock();
    iec60958* curr = stream;
    ctr.Unlock();

    if (curr)
      goto out;

    TEST(dvb >= (size_t)4) {
      // byte0    (substream number)
      // byte1    (Number of frames which begin in this package)
      // byte2, byte3 (offset to frame which corresponds to PTS value)
      uint_32 ul = dvb;
      uint_8  ub = (uint_8)(ul>>24);
      uint_8  tr = (ub & 0x1F) + 0x21;
      off_t   o  = (off_t) (ul & 0x0000ffff);

      bool isDVD = false;
      uint_8 m;

      switch (ub) {
      case 0x80 ... 0x87:                 // AC3 from e.g. DVD
          if (o == 0) o = 1;              // broken audio sub header
          o += 3;
          if (o >= rest)                  // word magic
            break;
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (id != ub)
            break;
#endif
          dvb += o;
          off += o;
          isDVD = true;
      case 0x0b:
          if ((uint_16)dvb != AC3magic)
            break;
          curr = &ac3;
          clear_setup(AUDIO);
          curr->Reset(setup.flags);
          curr->isDVD = isDVD;
          curr->track = ((isDVD) ? tr : 0x21);
          goto out;                       // found, go out
      case 0x88 ... 0x8f:                 // DTS
          if (o == 0) o = 1;              // broken audio sub header
          o += 3;
          if (o+2 >= rest)                // double word magic
            break;
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (id != ub)
            break;
#endif
          dvb += o;
          off += o;
          isDVD = true;
      case 0x7f:
          if ((uint_32)dvb != DTSmagic)
            break;
          curr = &dts;
          clear_setup(AUDIO);
          curr->Reset(setup.flags);
          curr->isDVD = isDVD;
          curr->track = ((isDVD) ? tr : 0x21);
          goto out;                       // found, go out
      case 0xa0 ... 0xa7:                 // Linear PCM Audio
      // byte4    (emphasis, mute, reserved, 4-0: frame number modulo 20)
      // byte5    (7-6: Quantization, 5-4: Sample rate, reserved, 2-0: channels-1)
      // byte6    (Dynamic range)
          if (o == 3) {             // broken stream
            dvb++;
            uint_32 ui = dvb;
            dvb += 4;
            m = dvb;
            if ((ui == 0) && (m == 0))
                goto out;                 // Don't support broken streams (dvd plugin).
            o = 7;
          } else {
            dvb += 5;
            m = dvb;
          }
          off += o;

          if (o >= rest)
            break;
#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (id != ub)
            break;
#endif
          curr = &pcm;
          set_setup(AUDIO);
          curr->Reset(setup.flags);
          curr->isDVD = true;
          curr->track = tr;

          switch (m & 0x30) {
          case 0x00: curr->sample_rate = 48000;
            break;
          case 0x20: curr->sample_rate = 44100;
            break;
          case 0x30: curr->sample_rate = 32000;
            break;
          case 0x10:                      // 96kHz  currently not supported
          default:   curr = NULL;
            break;
          }
          switch (m & 0xC0) {
          case 0x00:                      // 16 bit samples
            break;
          case 0x40:                      // 20 bit currently not supported
          case 0x80:                      // 24 bit currently not supported
          default:   curr = NULL;
            break;
          }
          switch (m & 0x07) {
          case 0x00:                      // 1 channel
          case 0x01:                      // 2 channels
            break;
          default:   curr = NULL;         // More channels not supported
            break;
          }
          goto out;                       // found, go out (even if not supported)
      default:
          dvb++;
          off++;
          while (dvb > (size_t)2) {
            if ((uint_16)dvb == AC3magic) {
                curr = &ac3;
                clear_setup(AUDIO);
                curr->Reset(setup.flags);
                curr->isDVD = false;
                curr->track = 0x21;
                break;
            }
            if ((dvb > (size_t)4) && (uint_32)dvb == DTSmagic) {
                curr = &dts;
                clear_setup(AUDIO);
                curr->Reset(setup.flags);
                curr->isDVD = false;
                curr->track = 0x21;
                break;
            }
            dvb++;
            off++;
          }
          goto out;
      }
    } END (dvb);

out:
    ctr.Lock();
    stream = curr;
    ctr.Unlock();
    return (curr != NULL);
}

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
inline bool cReplayOutSPDif::DigestPayOfMP2(const uchar *const b, int &off, const int cnt, const uchar id)
#else
inline bool cReplayOutSPDif::DigestPayOfMP2(const uchar *const b, int &off, const int cnt)
#endif
{
    static const uint_32 samplerate_table[3] = {44100, 48000, 32000};
    const off_t rest = cnt-off;
    cHandle audio(&b[off], rest);
    ctr.Lock();
    iec60958* curr = stream;
    ctr.Unlock();

    FOREACH(audio >= (size_t)2) {
      uint_16 us = audio;
      bool ext   = ((us & 0x0010)>>4 == 0);     // First of last five bits
      bool lsf   = ((us & 0x0008)>>3 == 0);     // Second of last five bits
      char layer = 4 - ((us & 0x0006)>>1);      // Next two bits of the last five
                                    // Last bit is the protection bit

      if ((us & 0xffe0) != 0xffe0) {            // Sync start: The first eleven bits
          audio++;
          continue;
      }
      size_t o = audio.Offset();

      if ((layer == 4) || (!lsf && ext)) {      // Sanity checks for mpeg audio
          audio++;
          continue;
      }
      audio += 2;

      uint_8 ub = audio;
      if ((ub & 0xf0) >> 4 == 15) {       // Bit rate index
          audio++;
          continue;
      }
      if ((ub & 0x0c) >> 2 == 3) {        // Sample rate index
          audio++;
          continue;
      }

      uint_32 rate = samplerate_table[(ub & 0x0c) >> 2];
      if (lsf) {                    // Sample rate
          rate /= 2;
          if (ext)
            rate /= 2;
      }

      curr = &mp2;
      if (test_setup(MP2SPDIF))
          clear_setup(AUDIO);
      else
          set_setup(AUDIO);
      curr->Reset(setup.flags);
      curr->isDVD = false;
      curr->sample_rate = rate;
      off += o;
      break;

    } END (handle);

    ctr.Lock();
    stream = curr;
    ctr.Unlock();
    return (curr != NULL);
}

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
void cReplayOutSPDif::Play(const uchar *b, int cnt, uchar id)
#else
void cReplayOutSPDif::Play(const uchar *b, int cnt)
#endif
{
    static uint_16 skip = 0;
    cHandle play(b, cnt);
    bool pts = false;
    ctr.Lock();
    iec60958* curr = stream;
    ctr.Unlock();

    if (!test_setup(ACTIVE)) {
      if (test_flag(ACTIVE))
          Clear();
      goto out;
    }

    if (skip) {
      debug("cReplayOutSPDif::Receive(skip=%d)\n", skip);
      skip--;
      goto out;
    }

    if (test_setup(MUTE) || test_setup(LIVE)) {
      bounce->flush();
      goto out;
    }

    TEST (play >= (size_t)10) {
      uint_32 mag = play;
      play += 4;
      uint_16 len = (uint_16)play + 6;
      play += 2;
      uint_16 flg = play;
      play += 2;
      int     off = (uint_8)play + 9;

      if ((flg & 0xC000) != 0x8000)
          goto out;

      pts = ((flg & 0x0080) && (off >= (9+5)));

      switch (mag) {
      case PS1magic:                      // Audio of PES Private Stream 1

          if (curr == &mp2) {             // MP2 already active
            Clear();                // Reset
            goto out;
          }

          if (!test_flag(BOUNDARY)) {
          
            // If the stream provides PTS/DTS information and/or
            // the data in the pay load is marked as aligned we
            // assume that that we'll find immediately the sync words
            // of the pay load its self or the corresponding sub
            // stream identification with the offset to the pay load.
            if ((flg & 0x00C0) || (flg & 0x0400)) {

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
                if (!ScanPayOfPS1(&b[0], off, cnt, id))
#else
                if (!ScanPayOfPS1(&b[0], off, cnt))
#endif
                  goto out;
                
                ctr.Lock();
                curr = stream;
                ctr.Unlock();
                if (!curr) goto out;      // Paranoid

                set_flag(BOUNDARY);       // At the beginning of a stream

                if (!test_flag(RUNNING)) {      // Start or continue after mute the
                  if (!pts)         // forwarding thread only on if we
                      goto out;           // got a Present Time Stamp (PTS).
                  Activate(true);
                  clear_flag(WASMUTED);
                } else if (test_flag(WASMUTED)) {
                  if (!pts)
                      goto out;
                  clear_flag(WASMUTED);
                }

            } else
                goto out;                 // Start only at a boundary of a stream

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          } else if (curr) {

            if (curr->isDVD) {
                // Check if DVD stream is still valid
                if (!OffsetToDvdOfPS1(b, off, cnt, id)) {
                  Clear();          // Reset
                  goto out;
                }
            } else {
                // Check if id of the stream is still valid
                if (id != 0xBD) {
                  Clear();          // Reset
                  goto out;
                }
            }
          }
#else
          } else if (curr && curr->isDVD) {

            // Check if DVD stream is still valid
            if (!OffsetToDvdOfPS1(b, off, cnt)) {
                Clear();                  // Reset
                goto out;
            }
          }
#endif
          break;
      case 0x000001c0 ... 0x000001df:           // Audio of PES Mpeg Audio frame
          if (!test_setup(MP2ENABLE))
            goto out;

          if (curr) {
            uint_8 track = ((uint_8)(mag & 0xFF) - 0xC0) + 1;

            if (curr != &mp2) {           // PS1 already active
                Clear();                  // Reset
                goto out;
            }
            if (curr->track != track) {   // Wrong track
                Clear();                  // Reset
                goto out;
            }
          }

          if (test_flag(BOUNDARY))
            break;
                                    // Start only at a boundary of a stream
          if (!(flg & 0x00C0) && !(flg & 0x0400))
            goto out;

#if defined(VDRVERSNUM) && VDRVERSNUM >= 10321
          if (!DigestPayOfMP2(&b[0], off, cnt, id))
#else
          if (!DigestPayOfMP2(&b[0], off, cnt))
#endif
            goto out;

          ctr.Lock();
          curr = stream;
          ctr.Unlock();
          if (!curr) goto out;            // Paranoid

          set_flag(BOUNDARY);             // At the beginning of a stream

          if (!test_flag(RUNNING)) {            // Start or continue after mute the
            if (!pts)               // forwarding thread only on if we
                goto out;                 // got a Present Time Stamp (PTS).
            Activate(true);
            clear_flag(WASMUTED);
          } else if (test_flag(WASMUTED)) {
            if (!pts)
                goto out;
            clear_flag(WASMUTED);
          }

          curr->track = ((uint_8)(mag & 0xFF) - 0xC0) + 1;

          break;
      default:
          goto out;
      }

      if (curr) {
          const uint_8 * buf = (const uint_8 *)&b[off];
          const size_t transmit = min(len - off, cnt);
#if 1
          const bool start = curr->Count(buf, buf+transmit);
#else
          const bool start = true;
#endif
          //
          // Initial synchronization
          //
          if (!curr->pts.synch()) {

            if (!curr->pts.stcsync(pts))  // Check if STC has constant time flow
                goto out;                 // to avoid old data in bounce buffer

            if (pts) {              // PTS given, remember value than
                play++;             // ...
                curr->pts.mark(play(5));  // and start the sync engine now
            } else {
                if (!curr->pts.mark())
                  goto out;         // Currently not started yet
            }

            curr->pts.synch(true);        // We are done here
          }

          if (!bounce->store(buf, transmit, start))
            skip = 2;

      }

    } END (play);
out:
    return;
}

void cReplayOutSPDif::Mute(bool onoff)
{
    debug("cReplayOutSPDif::Mute(%d) called\n", onoff);
    if ((test_setup(MUTE) ? true : false) == onoff)
      goto out;

    clear_setup(STILLPIC);
    if (onoff) {
      cDevice *PrimaryDevice = cDevice::PrimaryDevice();
      LOCK_THREAD;
      ctr.Lock();
      iec60958* curr = stream;
      ctr.Unlock();

      set_setup(MUTE);
      if (PrimaryDevice && !PrimaryDevice->IsMute())
          set_setup(STILLPIC);
      spdifDev->Pause(onoff);
      if (curr)
          curr->Clear();
      bounce->flush();
      bounce->signal();
      clear_flag(BOUNDARY);
    } else
      clear_setup(MUTE);
out:
    if (SPDIFmute) {    // Normal TV mode
      char *cmd = NULL;
      asprintf(&cmd, "%s %s 2> /dev/null", SPDIFmute, test_setup(MUTE) ? "mute" : "unmute");
      if (cmd) {
          if (test_setup(ACTIVE)) {
            if (test_setup(MP2ENABLE))
                setenv("loop", "off", 1);
            else
                setenv("loop", "on", 1);
          } else
            setenv("loop", "on", 1);
          system(cmd);
          free(cmd);
          cmd = NULL;
      }
    }
    return;
}

void cReplayOutSPDif::Clear(void)
{
    cDevice *PrimaryDevice = cDevice::PrimaryDevice();

    debug("cReplayOutSPDif::Clear() called\n");
    Activate(false);
    ctr.Lock();
    iec60958* curr = stream;
    ctr.Unlock();
    if (curr) {
      LOCK_THREAD;
      curr->Reset();
      bounce->flush();
      bounce->signal();
    }
    ctr.Lock();
    stream = NULL;
    ctr.Unlock();
    clear_flag(BOUNDARY);
    clear_setup(STILLPIC);
    if (PrimaryDevice)
      Mute(PrimaryDevice->IsMute());
}

Generated by  Doxygen 1.6.0   Back to index