[mythtv] Random blockiness that probably isn't CPU or PCI bus bandwidth-related

Eric Anderson rico99 at sbcglobal.net
Tue Jan 18 17:40:55 EST 2005


Kyle -

I have been seeing a very similar problem on my system (2.6-GHz 
hyperthreaded P4)
with two HD-3000 cards. And it doesn't happen every time. It actually 
seems to happen
more often with the latest CVS (with John Poet's ringbuffer code). With 
my own version
of the ringbuffer code substituted, it happens a lot less.  I'm not 
sure why. With the latest
CVS, it happens maybe 1 out of 5 recordings or so? With my version of 
the ringbuffer
code, maybe 1 time out of 20-30 recordings. (Hard to say, haven't seen 
it that often
lately.)

[I'll attach my version of the ringbuffer if you want to try it... John 
has claimed he wasn't
able to get the performance he needed for recording with 3 cards 
without using a highly
tuned version, but I'm not sure if he ever tried my final version of 
the ringbuffer. One
difference is that my version uses a single wrap-around buffer and 
works out of that
buffer -- in an attempt to reduce the number of extra copies.]

When the bug occurs, data starts being corrupted part way through a 
recording. And
it continues to be corrupted pretty much until the end of the 
recording. I was wondering
exactly what was broken in the recording...

So yesterday I wrote a small program to scan through the saved 
transport stream to try to
figure out *how* the data was corrupted. What I found is sets of 
duplicate packets. Thus,
I am starting to think that this is a driver issue.

I've attached a test program (xsum188.c) in case you want to see if 
you're running into
the same problem. The program is a quick hack, but it may help reveal 
if you have
repeated data in your stream. It computes checksums over the first 
50,000 TS packets
that it sees. And then it runs 50K x 50K comparisons on all of the 
checksums that it
sees and reports duplicates. (Doesn't handle cases of >2 duplicates. 
Probably could
be extended to do that...)

Here is an example of what I see:

% gcc -o xsum188 -O3 xsum188.c
% dd if=bad.nuv bs=188 skip=7416480 count=50000 > bad_short.nuv
% ./xsum188 <  bad_short.nuv   >   bad_short.nuv.cksum

% grep GOP bad_short.nuv.cksum
pkt:00000 [4760313d] 58d0b676    [GOP t=0b2d6780]
pkt:07727 [47603135] ce418a12    [GOP t=0b2da780]
pkt:10694 [4760313f] 889c74e2 !  del= +830[GOP t=0b2dc780]
pkt:11524 [4760313f] 889c74e2 !  del=  -830[GOP t=0b2dc780]
pkt:16110 [47603133] 4b545ea0    [GOP t=0b2de780]
pkt:20839 [47603133] 09e2492f    [GOP t=0b2e0780]
pkt:25554 [4760313e] ca5db35d    [GOP t=0b2e2780]
pkt:30033 [47603134] 846c1d6d    [GOP t=0b2e4780]
pkt:33642 [47603138] 2da8073b    [GOP t=0b2e6780]
pkt:38316 [47603137] e91870ad    [GOP t=0b2e8780]

Notice how I got a duplicate GOP for t=0xb2dc780. And in fact, the 
timestamps
went d6, da, dc, dc, de, e0, etc... In other words, we skipped d8, and 
doubled
up on dc. This is consistent with d8 being overwritten with da, da 
being overwritten
with dc, and then the overruns stopping and going back to normal (ie 
dc, de, e0...).

In fact, I had started out by looking only at the GOP timestamps. But 
then I
decided to also compare checksums of the TS packets. So if I look at the
data around the duplicate GOP, I see this:

pkt:10682 [47203116] 244d45e9
pkt:10683 [47203117] 75642fb2
pkt:10684 [4720341e] 74b3955f
pkt:10685 [47203117] b33620aa !  del= +830
pkt:10686 [47203118] 1a32c97b !  del= +830
pkt:10687 [47203119] 27d35eb9 !  del= +830
pkt:10688 [4720311a] 3db1ba3e !  del= +830
pkt:10689 [4720311b] a7bd387d !  del= +830
pkt:10690 [4720311c] ab3376b4 !  del= +830
pkt:10691 [4720341f] 1c8df000 !  del= +830
pkt:10692 [4720311d] 83325a60 !  del= +830
pkt:10693 [4720311e] b80ef2d7 !  del= +830
pkt:10694 [4760313f] 889c74e2 !  del= +830[GOP t=0b2dc780]
pkt:10695 [47203110] a72a617b !  del= +830
pkt:10696 [47203111] 203a8d7e !  del= +830
pkt:10697 [47203112] d595a7df !  del= +830
pkt:10698 [47203113] 18091d71 !  del= +830
pkt:10699 [47203114] 5608e305 !  del= +830
pkt:10700 [47203115] bf8acfbe !  del= +830
pkt:10701 [47203116] faadfdf0 !  del= +830
pkt:10702 [47203117] 70c50c73 !  del= +830
pkt:10703 [47203118] 389f6833 !  del= +830
pkt:10704 [47203119] b56a1ee9 !  del= +830
pkt:10705 [4720311e] 88aeb213
pkt:10706 [4720311f] a6da992f
pkt:10707 [47203110] cbe19a29

So this sequence of packets (b33620aa, 1a32c97b, etc...) appear later 
on in the
stream. In fact, 830 packets later. Thus there must have been an 
overrun of some
kind. But I did *NOT* get a message from the driver in my kernel log 
while this
stream was being recorded. I only found out it was corrupted when I 
tried to play
it back. (And it's pretty once the dups start happening.) Note that the 
amount of
packets between the duplicates varies between 753 and 1148. [Since not 
all packets
received are part of the stream being recorded, it is conceivable to 
see variation like
this.]

I've only looked at a few places so I can't say for certain I 
understand everything
that's going on. But I do know *somewhere* along the line I'm getting 
duplicated
runs of packets.

Note that if I run this same program on a "good" stream, I see no 
duplicates.
(Other than PAT packets, etc. -- but I've made an attempt to filter 
those out using
the program.)

I would be interested to know if others have seen similar problems.

My particulars:

    Fedora Core 2 - Kernel rev  2.6.5
    HD3000  driver rev 1.4   (Note: I tweaked the buffer size in 
cx88-atsc.c to
                                                  be 188*1536 instead of 
188*512. And I disabled
                                                  the audio thread in 
cx88-video.c that just prints
                                                  messages and doesn't 
seem to do anything else. I still need
                                                  to try reverting this 
to a stock version to see if it helps.)
   Latest myth CVS (w/ John Poet's ringbuffer code)  -- seems to happen 
more often with this
   Latest myth CVS (w/ my version of ringbuffer code)  -- I've seen this 
only once in the last week
   Pentium 4 @ 2.6-GHz Hyperthreading enabled
   2 x HD3000 cards
   1 x Sony gigapocket tuner card (using ivtv) -- not recording when 
this bug occurs

Attachments:
            xsum188.c -- in case you want to analyze a corrupted stream.
            diffs             -- My diffs for HD3000 rev 1.4.

The next thing I was going to do was try to add some instrumentation 
code to the
HD3000 driver. Because I *think* the card and/or driver are getting 
into a bad state
at some point which is causing all of these overruns; and it doesn't 
recover for the
duration of the recording.

Note: I don't even lose packet sync. It's just packets clobbering other 
packets. (Which
is consistent with data being clobbered in the driver, because the 
driver deals with
multiples of 188-bytes internally.)

Suggestions on how to proceed welcome!   (other than buying a different 
kind of card... =))

-Eric


On Jan 10, 2005, at 7:15 AM, Kyle Rose wrote:

> Daniel Thor Kristjansson <danielk at mrl.nyu.edu> writes:
>
>> This doesn't really jibe with my experience, unless you are running 
>> out
>> of CPU, which shouldn't be the case for a P4 3.2Ghz machine.
>
> Well, the problem is evident in the recording itself, so it may be
> that the 1.533 athlon isn't enough.  But after the recording starts,
> (which for some reason uses a huge amount of CPU), the thing never
> uses more than 3% of the CPU.  As I said, I doubt it's interrupt
> latency, or the driver would report buffer overruns, but something
> somewhere is losing data only when both capture cards are being used
> at once.
>
>> Of course I
>> do compile with extra optimizations, even in debug mode. If it is not
>> the CPU, do the following:
>
>> Make sure there are errors in the stream.
>>  Yes - If there are is the signal level too low?
>>    Yes - nothing we can do
>>    No  - there may be a bug in the ringbuffer
>>  No - there may be bug in ffmpeg
>
> ffmpeg isn't relevant to recording, so it isn't that.  Signal strength
> is always >85%, and shouldn't fluctuate when more than one card is
> being used; nonetheless, I'll check that because there could be some
> kind of wacky interference going on.
>
> Cheers,
> Kyle


-------------- next part --------------
#include <endian.h>
//#include <machine/endian.h>

main()
{
    int i,j;
    unsigned int s[64];
    int xsum;
    int pkt=0;
    int xsums[50000];
    unsigned int data_d[50000];
    unsigned int data_e[50000];
    unsigned int data_f[50000];
    unsigned int pids[50000];
    int xsumcnt=0;
    int xsumlast=0;
    int match;
    int delta;
    
    for (i=0;i<64;i++) s[i] = 0;
    for (i=0;i<50000;i++) xsums[i] = 0;

    while (read(0,s,188)) {
	xsum = 0;
	for (i=0;i<47;i++) {
	    xsum += s[i];
	}
	// Append
	xsums[xsumlast] = xsum;
	data_d[xsumlast] = ntohl(s[0xd]);
	data_e[xsumlast] = ntohl(s[0xe]);
	data_f[xsumlast] = ntohl(s[0xf]);
	pids[xsumlast]   = ntohl(s[0]);
	if (++xsumlast == 50000) break;
    }

    for (i=0;i<xsumlast;i++) {

	// Search for match
	match = 0;
	for (j=0;j<50000;j++) {
	    if (i==j) continue;
	    if (xsums[i] == xsums[j]) {
		delta=(j-i);
		match=1;
	    }
	}

	// Ignore PAT duplicates
	if ((pids[i] & 0x0f00) == 0) match = 0;

	printf("pkt:%05d [%08x] %08x %c  ", pkt++, pids[i], xsums[i], match ? '!' : ' ');
	if (match) printf("del= %c%d",(delta>=0) ? '+' : ' ', delta);
	if ((data_d[i] == 0x1) && ((data_e[i] >> 24) == 0xb8)) {
	    printf("[GOP t=%08x]", (data_e[i] << 8) | (data_f[i] >> 24));
	}
	printf("\n");
    }
}
-------------- next part --------------
  
   
-------------- next part --------------
A non-text attachment was scrubbed...
Name: diffs
Type: application/octet-stream
Size: 1074 bytes
Desc: not available
Url : http://mythtv.org/pipermail/mythtv-dev/attachments/20050118/b5ee5288/diffs.obj
-------------- next part --------------
  
  
-------------- next part --------------
/* HDTVRecorder
   Version 0.1
   July 4th, 2003
   GPL License (c)
   By Brandon Beattie

   Portions of this code taken from mpegrecorder

   Modified Oct. 2003 by Doug Larrick
   * decodes MPEG-TS locally (does not use libavformat) for flexibility
   * output format is MPEG-TS omitting unneeded PIDs, for smaller files
     and no audio stutter on stations with >1 audio stream
   * Emits proper keyframe data to recordedmarkup db, enabling seeking

   Oct. 22, 2003:
   * delay until GOP before writing output stream at start and reset
     (i.e. channel change)
   * Rewrite PIDs after channel change to be the same as before, so
     decoder can follow

   Oct. 27, 2003 by Jason Hoos:
   * if no GOP is detected within the first 30 frames of the stream, then
     assume that it's safe to treat a picture start as a GOP.

   Oct. 30, 2003 by Jason Hoos:
   * added code to rewrite PIDs in the MPEG PAT and PMT tables.  fixes 
     a problem with stations (particularly, WGN in Chicago) that list their 
     programs in reverse order in the PAT, which was confusing ffmpeg 
     (ffmpeg was looking for program 5, but only program 2 was left in 
     the stream, so it choked).

   Nov. 3, 2003 by Doug Larrick:
   * code to enable selecting a program number for stations with
     multiple programs
   * always renumber PIDs to match outgoing program number (#1:
     0x10 (base), 0x11 (video), 0x14 (audio))
   * change expected keyframe distance to 30 frames to see if this
     lets people seek past halfway in recordings

   Jan 30, 2004 by Daniel Kristjansson
   * broke out tspacket to handle TS packets
   * added CRC check on PAT & PMT packets
   Sept 27, 2004
   * added decoding of most ATSC Tables
   * added multiple audio support


   References / example code: 
     ATSC standards a.54, a.69 (www.atsc.org)
     ts2pes from mpegutils from dvb (www.linuxtv.org)
     bbinfo from Brent Beyeler, beyeler at home.com
*/

#include <iostream>
#include <pthread.h>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <ctime>
#include "videodev_myth.h"

using namespace std;

#include "hdtvrecorder.h"
#include "RingBuffer.h"
#include "mythcontext.h"
#include "programinfo.h"
#include "channel.h"
#include "mpegtables.h"
#include "atscstreamdata.h"

extern "C" {
#include "../libavcodec/avcodec.h"
#include "../libavformat/avformat.h"
#include "../libavformat/mpegts.h"
}

#define REPORT_RING_STATS 1
#define REPORT_TS_STATS   0

#define DEFAULT_SUBCHANNEL 1

#define WHACK_A_BUG_VIDEO 0
#define WHACK_A_BUG_AUDIO 0

#if FAKE_VIDEO
    static int fake_video_index = 0;
#define FAKE_VIDEO_NUM 4
    static const char* FAKE_VIDEO_FILES[FAKE_VIDEO_NUM] =
        {
            "/video/abc.ts",
            "/video/wb.ts",
            "/video/abc2.ts",
            "/video/nbc.ts",
        };
#endif

HDTVRecorder::HDTVRecorder()
    : DTVRecorder(), _atsc_stream_data(0), _resync_count(0)
{
    _error = false;
    _atsc_stream_data = new ATSCStreamData(DEFAULT_SUBCHANNEL);

    ringbuf.size = 16 * 1024 * 1024;
    ringbuf.wrapextra = 2048;  // ~11 packets
    ringbuf.minreadsize = 2048;
    ringbuf.maxreadsize = 128 * 1024;
    ringbuf.writesize = 2 * 1024 * 1024;

    if ((ringbuf.buffer = new unsigned char[ringbuf.size + ringbuf.wrapextra]) == NULL) {
        VERBOSE(VB_IMPORTANT, "Failed to allocate HDTVRecorder ring buffer.");
	_error = true;
    }
    else {
        // make valgrind happy, initialize buffer memory
	memset(ringbuf.buffer, 0xFF, ringbuf.size + ringbuf.wrapextra);

        VERBOSE(VB_RECORD, QString("HD ring buffer size %1 KB")
		.arg(ringbuf.size/1024));
   }

    pthread_mutex_init(&ringbuf.mutex, NULL);
    pthread_cond_init(&ringbuf.pausewait, NULL);
    pthread_cond_init(&ringbuf.writewait, NULL);
    pthread_cond_init(&ringbuf.readwait, NULL);

}

HDTVRecorder::~HDTVRecorder()
{
    if (_stream_fd >= 0)
        close(_stream_fd);
    if (_atsc_stream_data)
        delete _atsc_stream_data;

    pthread_mutex_destroy(&ringbuf.mutex);
    pthread_cond_destroy(&ringbuf.pausewait);
    pthread_cond_destroy(&ringbuf.writewait);
    pthread_cond_destroy(&ringbuf.readwait);
}

void HDTVRecorder::SetOptionsFromProfile(RecordingProfile *profile,
                                         const QString &videodev,
                                         const QString &audiodev,
                                         const QString &vbidev, int ispip)
{
    (void)audiodev;
    (void)vbidev;
    (void)profile;
    (void)ispip;

    SetOption("videodevice", videodev);
    SetOption("tvformat", gContext->GetSetting("TVFormat"));
    SetOption("vbiformat", gContext->GetSetting("VbiFormat"));
}

bool HDTVRecorder::Open()
{
    if (!_atsc_stream_data || !ringbuf.buffer) 
        return false;

#if FAKE_VIDEO
    _stream_fd = open(FAKE_VIDEO_FILES[fake_video_index], O_RDWR);
    VERBOSE(VB_IMPORTANT, QString("Opened fake video source %1").arg(FAKE_VIDEO_FILES[fake_video_index]));
    fake_video_index = (fake_video_index+1)%FAKE_VIDEO_NUM;
#else
    _stream_fd = open(videodevice.ascii(), O_RDWR);
#endif
    if (_stream_fd <= 0)
    {
        VERBOSE(VB_IMPORTANT, QString("Can't open video device: %1 chanfd = %2")
                .arg(videodevice).arg(_stream_fd));
        perror("open video:");
    }
    return (_stream_fd > 0);
}

void HDTVRecorder::Close()
{
    if (_stream_fd < 0) return;
    int ret = close(_stream_fd);
    if (ret < 0) {
	perror("close");
    }
    _stream_fd = -1;
}


void HDTVRecorder::StartRecording(void)
{
    if (!Open())
    {
        _error = true;
        return;
    }

    _error = false;
    _request_recording = true;
    _request_pause = false;
    _recording = true;
    _sync_count = 0;

    ringbuf.rdidx = 0;
    ringbuf.wridx = 0;
    ringbuf.eof = false;
    ringbuf.max_used = 0;
    ringbuf.avg_cnt = 0;
    ringbuf.avg_used = 0;
    ringbuf.tlast = 0;

    ringbuf.request_recording = true;
    ringbuf.request_pause = false;

    ringbuf.recording = true;
    ringbuf.paused = false;

    pthread_create(&ringbuf.thread, NULL, StartRingBuffer, reinterpret_cast<void *>(this));

    // TRANSFER DATA
    while (_recording)
    {
	int fifo_depth;
	int process_len;
	int remainder;

	// Hand off pause request
	pthread_mutex_lock(&ringbuf.mutex);
	ringbuf.request_pause = _request_pause;
	ringbuf.request_recording = _request_recording;
	_recording = ringbuf.recording;

	// Wait for response
	if (_request_pause) {
	    _paused = ringbuf.paused;
	    if (!_paused) {
		pthread_cond_wait(&ringbuf.pausewait, &ringbuf.mutex);
		if (ringbuf.paused) {
		    pauseWait.wakeAll();
		} else {
		    VERBOSE(VB_IMPORTANT,
			    QString("HD ringbuf thread not pausing??"));
		}
	    }
	    pthread_mutex_unlock(&ringbuf.mutex);
	    usleep(1000);
	    continue;
	}
	_paused = ringbuf.paused;

	fifo_depth = ringbuf.wridx - ringbuf.rdidx;

	// Bytes to process
	process_len = fifo_depth;

	// Wrap case
	if (process_len < 0) {

	    // For stats
	    fifo_depth += ringbuf.size;

	    // Cap process_len
	    process_len = ringbuf.size - ringbuf.rdidx;

	    // The writer always copies ringbuf.wrapextra bytes *beyond* the 
	    // end of the fifo. This gives us a minimum number of contiguous
	    // bytes for routines that can't deal with the wrap.
	    process_len += (ringbuf.wridx > ringbuf.wrapextra) ?
		ringbuf.wrapextra : ringbuf.wridx;
	}
	
	// Don't process more than maxreadsize
	if (process_len > ringbuf.maxreadsize) {
	    process_len = ringbuf.maxreadsize;
	}

	// Not enough data to work on?
	if (fifo_depth < ringbuf.minreadsize) {

	    // Wait for data
	    if (_recording) {
		pthread_cond_wait(&ringbuf.readwait, &ringbuf.mutex);
	    }
	    pthread_mutex_unlock(&ringbuf.mutex);
	    continue;
	}
	pthread_mutex_unlock(&ringbuf.mutex);

#if REPORT_RING_STATS
	// Update Stats/Watermarks -- print information every minute.
	if (fifo_depth > ringbuf.max_used) {
	    ringbuf.max_used = fifo_depth;
	}
	ringbuf.avg_used = ((ringbuf.avg_used * ringbuf.avg_cnt) + fifo_depth)
	    / ++ringbuf.avg_cnt;
	int t = time(NULL);
	if (t >= ringbuf.tlast + 60) {
	    VERBOSE(VB_RECORD, QString("%1 ringbuf avg %2% max %3% cur %4% samples:%5")
		    .arg(videodevice)
		    .arg((static_cast<double>(ringbuf.avg_used) / ringbuf.size) * 100.0)
		    .arg((static_cast<double>(ringbuf.max_used) / ringbuf.size) * 100.0)
		    .arg((static_cast<double>(fifo_depth) / ringbuf.size) * 100.0)
		    .arg(ringbuf.avg_cnt));
	    ringbuf.tlast = t;
	    ringbuf.avg_cnt = 0;
	    ringbuf.avg_used = 0;
	}
#endif

	// Process data
	remainder = ProcessData(&ringbuf.buffer[ringbuf.rdidx], process_len);

	pthread_mutex_lock(&ringbuf.mutex);
	ringbuf.rdidx += (process_len - remainder);
	if (ringbuf.rdidx >= ringbuf.size) {
	    ringbuf.rdidx -= ringbuf.size;
	}
	pthread_cond_signal(&ringbuf.writewait);
	pthread_mutex_unlock(&ringbuf.mutex);
    }

    if (ringbuf.eof) {
	VERBOSE(VB_IMPORTANT, QString("%1 - HD8 end of file found in packet").arg(videodevice));
    }

    // Clean up
    pthread_join(ringbuf.thread, NULL);
    FinishRecording();
    _recording = false;
}


void *HDTVRecorder::StartRingBuffer(void *arg)
{
    HDTVRecorder *hdtvrec = reinterpret_cast<HDTVRecorder *>(arg);

    hdtvrec->fill_ringbuffer();
    return(NULL);
}

int HDTVRecorder::fill_ringbuffer()
{
    int       len;
    int       read_bytes;
    int       fifo_space;
    bool      recording = true;
    bool      paused = false;

    while (recording) {

	// Update run/pause status
	pthread_mutex_lock(&ringbuf.mutex);
	paused = ringbuf.request_pause;
	recording = ringbuf.request_recording;

	// Echo back the pause
	if (ringbuf.paused != paused) {
	    ringbuf.paused = paused;
	    pthread_cond_signal(&ringbuf.pausewait);
	}

	// Get fifo depth
	fifo_space = ringbuf.rdidx - ringbuf.wridx;
	if (fifo_space <= 0) fifo_space += ringbuf.size;

	// Enough space? If not, wait.
	if (fifo_space < ringbuf.writesize) {
	    pthread_cond_wait(&ringbuf.writewait, &ringbuf.mutex);
	    pthread_mutex_unlock(&ringbuf.mutex);
	    continue;
	}
	pthread_mutex_unlock(&ringbuf.mutex);

	// How much can we safely read?
	read_bytes = ringbuf.writesize;
	if (read_bytes + ringbuf.wridx > ringbuf.size) {
	    read_bytes = fifo_space - ringbuf.wridx;
	}

	len = read(_stream_fd, &ringbuf.buffer[ringbuf.wridx], read_bytes);
	if (len < 0) {
	    VERBOSE(VB_IMPORTANT, QString("%1 - HD: Read from device failed!").arg(videodevice));
	    perror("read");
	    _error = 1;
	    continue;
	}
	// EOF?
	if (len == 0) {
	    pthread_mutex_lock(&ringbuf.mutex);
	    ringbuf.eof = true;
	    pthread_mutex_unlock(&ringbuf.mutex);
	    break;
	}

	// If we loaded below ringbuf.wrapextra, refresh end of buffer
	if (ringbuf.wridx < ringbuf.wrapextra) {
	    memmove(ringbuf.buffer + ringbuf.size,  // to end
		    ringbuf.buffer,                 // from start
		    ringbuf.wrapextra);
	}

	// Write pointer increment
	if (!paused) {
	    pthread_mutex_lock(&ringbuf.mutex);
	    ringbuf.wridx += len;
	    if (ringbuf.wridx >= ringbuf.size) ringbuf.wridx -= ringbuf.size;
	    pthread_cond_signal(&ringbuf.readwait);
	    pthread_mutex_unlock(&ringbuf.mutex);
	}
    }

    pthread_mutex_lock(&ringbuf.mutex);
    ringbuf.recording = false;
    pthread_cond_signal(&ringbuf.readwait);
    pthread_mutex_unlock(&ringbuf.mutex);

    Close();
    pthread_exit(reinterpret_cast<void *>(0));
    return(0);
}


int HDTVRecorder::ResyncStream(unsigned char *buffer, int curr_pos, int len)
{
    // Search for repeated syncs
    const int syncpackets = 10;
    int i;
    int pos = curr_pos;

    // Search for repeating sync pattern
    while (pos + syncpackets * (int)TSPacket::SIZE < len) {
	for (i=0;i<syncpackets;i++) {
	    if (buffer[pos + i * TSPacket::SIZE] != SYNC_BYTE) {
		pos++;
		break;
	    }
	}
	if (i == syncpackets) return(pos);
    }
    return(-pos);  // Negative means try again
}

void HDTVRecorder::WritePAT() {
    if (StreamData()->PAT()) {
        ProgramAssociationTable* pat = StreamData()->PAT();
        int next = (pat->tsheader()->ContinuityCounter()+1)&0xf;
        pat->tsheader()->SetContinuityCounter(next);
        ringBuffer->Write(pat->tsheader()->data(), TSPacket::SIZE);
    }
}

#if WHACK_A_BUG_VIDEO
static int WABV_base_pid     = 0x100;
#define WABV_WAIT 60
static int WABV_wait_a_while = WABV_WAIT;
bool WABV_started = false;
#endif

#if WHACK_A_BUG_AUDIO
static int WABA_base_pid     = 0x200;
#define WABA_WAIT 60
static int WABA_wait_a_while = WABA_WAIT;
bool WABA_started = false;
#endif

void HDTVRecorder::WritePMT() {
    if (StreamData()->PMT()) {
        ProgramMapTable* pmt = StreamData()->PMT();
        int next = (pmt->tsheader()->ContinuityCounter()+1)&0xf;
        pmt->tsheader()->SetContinuityCounter(next);

#if WHACK_A_BUG_VIDEO
        WABV_wait_a_while--;
        if (WABV_wait_a_while<=0) {
            WABV_started = true;
            WABV_wait_a_while = WABV_WAIT;
            WABV_base_pid = (((WABV_base_pid-0x100)+1)%32)+0x100;
            VERBOSE(VB_IMPORTANT, QString("Whack a Bug: new video pid %1").arg(WABV_base_pid));
            // rewrite video pid
            assert(StreamID::MPEG2Video == StreamData()->PMT()->StreamType(0));
            const uint old_video_pid=StreamData()->PMT()->StreamPID(0);
            StreamData()->PMT()->SetStreamPID(0, WABV_base_pid);
            if (StreamData()->PMT()->PCRPID() == old_video_pid)
                StreamData()->PMT()->SetPCRPID(WABV_base_pid);
            StreamData()->PMT()->SetCRC(StreamData()->PMT()->CalcCRC());
            VERBOSE(VB_IMPORTANT, StreamData()->PMT()->toString());
        }
#endif
#if WHACK_A_BUG_AUDIO
        WABA_wait_a_while--;
        if (WABA_wait_a_while<=0) {
            WABA_started = true;
            WABA_wait_a_while = WABA_WAIT;
            WABA_base_pid = (((WABA_base_pid-0x200)+1)%32)+0x200;
            VERBOSE(VB_IMPORTANT, QString("Whack a Bug: new audio BASE pid %1").arg(WABA_base_pid));
            // rewrite audio pids
            for (uint i=0; i<StreamData()->PMT()->StreamCount(); i++) {
                if (StreamID::MPEG2Audio == StreamData()->PMT()->StreamType(i) ||
                    StreamID::MPEG2Audio == StreamData()->PMT()->StreamType(i)) {
                    const uint old_audio_pid = StreamData()->PMT()->StreamPID(i);
                    const uint new_audio_pid = WABA_base_pid + old_audio_pid;
                    StreamData()->PMT()->SetStreamPID(i, new_audio_pid);
                    if (StreamData()->PMT()->PCRPID() == old_audio_pid)
                        StreamData()->PMT()->SetPCRPID(new_audio_pid);
                    StreamData()->PMT()->SetCRC(StreamData()->PMT()->CalcCRC());
                    VERBOSE(VB_IMPORTANT, StreamData()->PMT()->toString());
                }
            }
        }
#endif

        ringBuffer->Write(pmt->tsheader()->data(), TSPacket::SIZE);
    }
}

void HDTVRecorder::HandleVideo(const TSPacket* tspacket)
{
    FindKeyframes(tspacket);
    // decoder needs video, of course (just this PID)
    // delay until first GOP to avoid decoder crash on res change
    if (_wait_for_keyframe && !_keyframe_seen)
        return;

#if WHACK_A_BUG_VIDEO
    if (WABV_started)
        ((TSPacket*)(tspacket))->SetPID(WABV_base_pid);
#endif

    ringBuffer->Write(tspacket->data(), TSPacket::SIZE);
}

void HDTVRecorder::HandleAudio(const TSPacket* tspacket)
{
    // decoder needs audio, of course (just this PID)
    if (_wait_for_keyframe && !_keyframe_seen)
        return;

#if WHACK_A_BUG_AUDIO
    if (WABA_started)
        ((TSPacket*)(tspacket))->SetPID(WABA_base_pid+tspacket->PID());
#endif

    ringBuffer->Write(tspacket->data(), TSPacket::SIZE);
}

bool HDTVRecorder::ProcessTSPacket(const TSPacket& tspacket)
{
    bool ok = !tspacket.TransportError();
    if (ok && !tspacket.ScramplingControl())
    {
        if (tspacket.HasAdaptationField())
            StreamData()->HandleAdaptationFieldControl(&tspacket);
        if (tspacket.HasPayload())
        {
            const unsigned int lpid = tspacket.PID();
            // Pass or reject frames based on PID, and parse info from them
            if (lpid == StreamData()->VideoPID())
                HandleVideo(&tspacket);
            else if (StreamData()->IsAudioPID(lpid))
                HandleAudio(&tspacket);
            else if (StreamData()->IsListeningPID(lpid))
                StreamData()->HandleTables(&tspacket, this);
            else if (StreamData()->IsWritingPID(lpid))
                ringBuffer->Write(tspacket.data(), TSPacket::SIZE);
            else if (StreamData()->VersionMGT()>=0)
                _ts_stats.IncrPIDCount(lpid);
        }
    }
    return ok;
}

int HDTVRecorder::ProcessData(unsigned char *buffer, int len)
{
    int pos = 0;
    const int unsyncpackets = 5;

    while (pos + 187 < len) // while we have a whole packet left
    {
	// If not synced, try to reacquire.
	if (_sync_count == 0) {
	    pos = ResyncStream(buffer, pos, len);
	    if (pos < 0) {
		// No sync found -- advance pointer.
		return len - (-pos);
	    }
	    _resync_count++;
	    VERBOSE(VB_RECORD, QString("%1 - Resync").arg(videodevice));
	}

	// Too many errors in a row, drop sync.
	if (buffer[pos] != SYNC_BYTE) {
	    if (_sync_count > 0) {
		_sync_count--;
		if (_sync_count == 0) {
		    VERBOSE(VB_RECORD, QString("%1 - Lost Sync").arg(videodevice));
		}
	    }
	    continue;
	}

	// Synced
	_sync_count = unsyncpackets;
	
        const TSPacket *pkt = reinterpret_cast<const TSPacket*>(&buffer[pos]);
	if (ProcessTSPacket(*pkt)) {
	    _ts_stats.IncrTSPacketCount();
#if REPORT_TS_STATS
          if (0 == _ts_stats.TSPacketCount()%1000000)
              VERBOSE(VB_RECORD, _ts_stats.toString());
#endif	  
	}
	pos += TSPacket::SIZE; // Advance to next TS packet
	// else // Let it resync in case of dropped bytes
	// buffer[pos] = SYNC_BYTE+1;
    }

    return len - pos;
}

void HDTVRecorder::Reset(void)
{
    DTVRecorder::Reset();

    write(0,"HDTVRecorder::Reset()\r\n",23);

    // Stats
    _sync_count = 0;
    _resync_count = 0;
    _ts_stats.Reset();

    if (curRecording && db_lock && db_conn)
    {
        pthread_mutex_lock(db_lock);
        MythContext::KickDatabase(db_conn);
        curRecording->ClearPositionMap(MARK_GOP_BYFRAME, db_conn);
        pthread_mutex_unlock(db_lock);
    }

    // Flush read/write pointers
    pthread_mutex_lock(&ringbuf.mutex);
    ringbuf.rdidx = 0;
    ringbuf.wridx = 0;
    ringbuf.eof = false;
    ringbuf.max_used = 0;
    ringbuf.avg_cnt = 0;
    ringbuf.avg_used = 0;
    ringbuf.tlast = 0;
    pthread_cond_signal(&ringbuf.readwait);
    pthread_cond_signal(&ringbuf.writewait);
    pthread_mutex_unlock(&ringbuf.mutex);

    StreamData()->Reset();
}

void HDTVRecorder::ChannelNameChanged(const QString& new_chan)
{
    if (!_atsc_stream_data || !ringbuf.buffer)
        return;

    _wait_for_keyframe = _wait_for_keyframe_option;

#if FAKE_VIDEO
    RecorderBase::ChannelNameChanged("51");
#else
    DTVRecorder::ChannelNameChanged(new_chan);
#endif

    // look up freqid
    pthread_mutex_lock(db_lock);
    QString thequery = QString("SELECT freqid "
                               "FROM channel WHERE channum = \"%1\";")
        .arg(curChannelName);
    QSqlQuery query = db_conn->exec(thequery);
    if (!query.isActive())
        MythContext::DBError("fetchtuningparamschanid", query);
    if (query.numRowsAffected() <= 0)
    {
        pthread_mutex_unlock(db_lock);
        return;
    }
    query.next();
    QString freqid(query.value(0).toString());
    VERBOSE(VB_RECORD, QString("Setting frequency for startRecording freqid: %1").arg(freqid));
    pthread_mutex_unlock(db_lock);

    int desired_subchannel = DEFAULT_SUBCHANNEL;
    int pos = freqid.find('-');
    if (pos != -1) 
        desired_subchannel = atoi(freqid.mid(pos+1).ascii());
    else
        VERBOSE(VB_IMPORTANT,
                QString("Error: Desired subchannel not specified in freqid \"%1\", Using %2.").arg(freqid).arg(desired_subchannel));
    StreamData()->Reset(desired_subchannel);
}
-------------- next part --------------
  
  
-------------- next part --------------
// -*- Mode: c++ -*-
/**
 *  HDTVRecorder
 *  Copyright (c) 2003-2004 by Brandon Beattie, Doug Larrick, 
 *    Jason Hoos, and Daniel Thor Kristjansson
 *  Device ringbuffer added by John Poet
 *  Distributed as part of MythTV under GPL v2 and later.
 */

#ifndef HDTVRECORDER_H_
#define HDTVRECORDER_H_

#include "dtvrecorder.h"
#include "tsstats.h"

struct AVFormatContext;
struct AVPacket;
class ATSCStreamData;

class HDTVRecorder : public DTVRecorder
{
    friend class ATSCStreamData;
    friend class TSPacketProcessor;
  public:
    HDTVRecorder();
   ~HDTVRecorder();

    void SetOptionsFromProfile(RecordingProfile *profile,
                               const QString &videodev,
                               const QString &audiodev,
                               const QString &vbidev, int ispip);

    void StartRecording(void);
    void Reset(void);

    bool Open(void);
    void Close(void);

    void ChannelNameChanged(const QString& new_freqid);

  private:
    int ProcessData(unsigned char *buffer, int len);
    bool ProcessTSPacket(const TSPacket& tspacket);
    void HandleVideo(const TSPacket* tspacket);
    void HandleAudio(const TSPacket* tspacket);

    int ResyncStream(unsigned char *buffer, int curr_pos, int len);

    void WritePAT();
    void WritePMT();

    ATSCStreamData* StreamData() { return _atsc_stream_data; }
    const ATSCStreamData* StreamData() const { return _atsc_stream_data; }

    ATSCStreamData* _atsc_stream_data;

    // statistics
    TSStats _ts_stats;
    long long _resync_count;
    int _sync_count;

    // Data for managing the device ringbuffer
    struct {
	unsigned char  *buffer;
	int             size;
	int             wrapextra;
	int             wridx;
	int             rdidx;
	int             minreadsize;
	int             maxreadsize;
	int             writesize;
	bool            eof;
	pthread_t       thread;
	pthread_mutex_t mutex;
	pthread_cond_t  pausewait;
	pthread_cond_t  readwait;
	pthread_cond_t  writewait;
	int             request_recording;
	int             request_pause;
	int             recording;
	int             paused;
	int             max_used;
	int             avg_used;
	int             avg_cnt;
	int             tlast;
    } ringbuf;

    static void *StartRingBuffer(void *arg);
    int fill_ringbuffer();
};

#endif
-------------- next part --------------





More information about the mythtv-dev mailing list