IPTVRecorder

From MythTV Official Wiki
Revision as of 12:37, 26 April 2013 by Stevegoodey (talk | contribs) (Slight corrections.)

Jump to: navigation, search

General MythTV recorder architecture

The five most important classes in the recorder architecture are:

 * TVRec -- same for all recorders
 * RingBuffer -- same for all recorders
 * RecorderBase
 * SignalMonitor
 * ChannelBase

TVRec

When either Scheduler or a TVPlay instance wants to start recording they call TVRec::StartRecording() with the ProgramInfo of the program they wish to record. Once recording the Scheduler or TVPlay do not need to end the recording, TVRec takes care of ending the recording.

TVRec runs its own event loop in TVRec::run(). This event loop predates the ability to run Qt event loops in any thread and is custom to TVRec. It handles three things: state change commands initiated from TVRec::ChangeState(), tuning requests from the tuningRequests deque, and it handles ending in progress recordings.

RingBuffer

This class provides the interface that RecorderBase uses to write data to disk. Internally it uses a ThreadedFileBuffer, but the most important thing to RecorderBase is that the RingBuffer::write() does not block for more than a few milliseconds at a time, unlike disk writes themselves which can block for a number of seconds on a system that is operating normally.

This class' public methods are thread-safe.

RecorderBase

This class does two main things:

    * Coordinates moving data from a device to a RingBuffer
    * Writes any metadata pulled from the streams to disk or database.

This class is a QRunnable and is given its own thread by TVRec.

This class' public methods are thread-safe.

Instances of this class are created when recording starts and destroyed when recordings end. With back-to-back recordings this class will live through the program transitions.

ChannelBase

This class handles changing channels.

In some cases this responsibility is delegated to other classes, but this class provides the interface that TVRec and SignalMonitor interact with.

This class' public methods are thread-safe.

Instances of this class are created when TVRec starts up, deleted when TVRec is torn down. Currently these are only destroyed on mythbackend shutdown, but the plan is for these to sometimes be torn down while the backend is running as part of the HTML configuration of the backend work.

SignalMonitor

This class tells us if there is data coming in on our device that we can record. These are started up between the time we tune to a channel using ChannelBase and start recording with RecorderBase. Using a signal monitor is only required if it is possible for a recorder to fail to tune to a channel. Currently all recorders employ a SignalMonitor.

Digital TV Recorders

Digital Television in the world today has some attributes that both make it more difficult to record than analog television but also has many commonalities that allow a great deal of code reuse. Most notably every DTV in operation today, from ATSC to DVB to OpenCable to ISDB are built on top of the MPEG ISO 11172 standard adopted in 1993 (except possibly China). And even more fortuituously they all use MPEG transport streams. There are two sets of data we care about in any MPEG transport stream. The first is Program System Information Tables or PSIP, and the other is the location of keyframes in the stream.

An MPEG Transport stream (MPEG-TS) contains 188 byte packets each containing a 12 bit PID and 184 bytes of payload. PID 0 always contains the Program Association Sections that make up the Program Association Table or PAT. The PAT in turn contains a list of PIDs and information on those PIDs. Most notably, it lists all the active Program Map Table PIDs, or PMT PIDs. Each PMT describes a 'channel' or 'program stream'. That is it lists all the video and audio stream PIDs associated with the program and optionally provides metadata about the program stream. These two tables are all we need to tune to an MPEG program stream. If a channel has it's sistandard field in the database set to 'mpeg' or 'opencable' then what we expect for a DTVSignalMonitor to be happy is a PAT containing the program number or 'serviceid' of the program in it's list, and then on the PID pointed to by the PAT a complete PMT. For ATSC and DVB streams some additional tables are required, but the concept is the same. For instance for ATSC we have 'major' and 'minor' channel numbers we find listed in the Virtual Channel Table or VCT and this VCT lists the program number, which we look up in the PAT to so we can find the complete PMT.

TBD

  *Explain keyframe finding done by DTVRecorder::FindXXXKeyframes()

We don't want everyone writing a recorder to need to be an expert in digital television to write a recorder. If you look at HDHRecorder::run() you will see these three lines:

   _stream_data->AddAVListener(this);
   _stream_data->AddWritingListener(this);
   _stream_handler->AddListener(_stream_data);

This line:

   _stream_data->AddAVListener(this);

Tells our MPEGStreamData instance to call this classes Audio/Video handling methods whenever it sees audio and video data. The MPEGStreamData instance is given to HDHRRecorder by TVRec and may very well be a DVBStreamData or a ATSCStreamData, but most recorders don't need to care about those details. Note: HDHRRecorder inherits from DTVRecorder so it doesn't actually need to implement and of these Audio/Video handling methods. DTVRecorder finds keyframes in the data and schedules the packets for writing to disk. The MPEGStreamData instance also performs all the necessary PID filtering. PID filtering is sometimes handled in elsewhere by the recorder, but this is only for efficiency reasons. For instance, the HDHomeRun can in most cases filter out PIDs we don't care about on the device itself and this avoids sending this data we will throw away over the network.

This line:

   _stream_data->AddWritingListener(this);

Tells our MPEGStreamData instance to send other data marked for writing to us as well. This might include something like the PCR timing stream when it is sent seperately from the audio and video streams.

This line:

   _stream_handler->AddListener(_stream_data);

Tells the StreamHandler instance that HDHRRecorder manages to write all it's data to our MPEGStreamData instance. StreamHandlers are not used by all recorders, in fact we could just read data from a some location in the HDHRRecoder::run() loop and write it to the MPEGStreamData by calling it's ProcessData() method. This is what the RecorderBase interface was originally written for, but StreamHandlers were added to support recording multiple programs recieved on the same device by sharing a device with several recorders each of which writes one file to disk.

As an important aside for someone writing a recorder, there are 7 main responsibilities Recorder::run() fulfills:

 1/ Calls Open() and exits if the open fails.
 2/ Sets recording to true under the pauseLock and wakes up any thread waiting for the recorder to start or stop
 3/ Calls StartNewFile()
 4/ Exits promptly if IsErrored() returns true
 5/ Exits promptly if IsRecordingRequested() returns false
 6/ Temporarily pauses recording if request_pause is true, resumes when request_pauuse is false.
 7/ After IsErrored() is true or IsRecordingRequested() is false:
    7.a/ Calls Close()
    7.b/ Calls FinishRecording();
    7.c/ Sets recording to false under the pauseLock and waits for any thread waiting for the recorder to start or stop

Number 6 is interesting and a possible source of error. There tends to be a fair bit of set up and tear down involved with the use of a Recorder, this is especially true with the analog framegrabber recorders MythTV originally supported. To avoid a teardown and rebuild when switching channels in LiveTV mode this pausing mechanism was devised. This probably wouldn't be designed this way today but it isn't that hard to get right either. All you need to do is ensure no data is written while the recorder is paused. In the case of the HDHRRecorder there is a custom HDHRRecorder::PauseAndWait() that just calls _stream_handler->RemoveListener(_stream_data); when the recorder is paused, and _stream_handler->AddListener(_stream_data); when it is unpaused.

In the HDHRRecorder::run() loop we spend most of our time sleeping in either PauseAndWait() or unpauseWait.wait(&pauseLock, 100); This is pretty typical of most recorders. You want this loop to be able to stop or pause recordings quickly because the quicker you repond the more quickly channel changes can happen.

TBD

* Explain {MPEG,DVB,ATSC,Scan}StreamData
* Explain StreamHandler classes
  + These are optional but they allow multiple recorders to access a data stream.
  + One per recording source (i.e. /dev/video0 or /dev/dvb/adapter0/frontend0).

IPTV Recorder Architecture

TBD

 * Explain how the lifetime of a StreamHandler is different in this recorder as compared with other recorders.
   + Each 'channel' is actually a recording source.
 * Explain the PacketBuffer, especially the RTPPacketBuffer.
 * Explain IPTVTuningData