Files
libstb-hal/libtriple/playback_td.cpp
svenhoefer 3da91b1463 - rename lt_debug => hal_debug; it's not only libtriple debugging
Signed-off-by: Thilo Graf <dbt@novatux.de>

- debuglevel fixed
2019-01-01 15:50:37 +01:00

1507 lines
34 KiB
C++

#include <config.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <cstring>
#include <map>
#include "playback_td.h"
#include "dmx_hal.h"
#include "audio_td.h"
#include "video_td.h"
#include "hal_debug.h"
#define hal_debug(args...) _hal_debug(HAL_DEBUG_PLAYBACK, this, args)
#define hal_info(args...) _hal_info(HAL_DEBUG_PLAYBACK, this, args)
#define hal_info_c(args...) _hal_info(HAL_DEBUG_PLAYBACK, NULL, args)
#include <tddevices.h>
#define DVR "/dev/" DEVICE_NAME_PVR
static int mp_syncPES(uint8_t *, int, bool quiet = false);
static int sync_ts(uint8_t *, int);
static inline uint16_t get_pid(uint8_t *buf);
static void *start_playthread(void *c);
static void playthread_cleanup_handler(void *);
static pthread_cond_t playback_ready_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t playback_ready_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t currpos_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t inbufpos_mutex = PTHREAD_MUTEX_INITIALIZER;
static int dvrfd = -1;
static int streamtype;
extern cDemux *videoDemux;
extern cDemux *audioDemux;
extern cVideo *videoDecoder;
extern cAudio *audioDecoder;
static const char *FILETYPE[] = {
"FILETYPE_UNKNOWN",
"FILETYPE_TS",
"FILETYPE_MPG",
"FILETYPE_VDR"
};
cPlayback::cPlayback(int)
{
hal_debug("%s\n", __FUNCTION__);
thread_started = false;
inbuf = NULL;
pesbuf = NULL;
filelist.clear();
curr_fileno = -1;
in_fd = -1;
streamtype = 0;
}
cPlayback::~cPlayback()
{
hal_debug("%s\n", __FUNCTION__);
Close();
}
bool cPlayback::Open(playmode_t mode)
{
static const char *PMODE[] = {
"PLAYMODE_TS",
"PLAYMODE_FILE"
};
hal_debug("%s: PlayMode = %s\n", __FUNCTION__, PMODE[mode]);
thread_started = false;
playMode = mode;
filetype = FILETYPE_TS;
playback_speed = 0;
last_size = 0;
_pts_end = 0;
astreams.clear();
memset(&cc, 0, 256);
return true;
}
//Used by Fileplay
void cPlayback::Close(void)
{
hal_info("%s\n", __FUNCTION__);
playstate = STATE_STOP;
if (thread_started)
{
hal_info("%s: before pthread_join\n", __FUNCTION__);
pthread_join(thread, NULL);
}
thread_started = false;
hal_info("%s: after pthread_join\n", __FUNCTION__);
mf_close();
filelist.clear();
if (inbuf)
free(inbuf);
inbuf = NULL;
if (pesbuf)
free(pesbuf);
pesbuf = NULL;
/* don't crash */
if (audioDecoder)
audioDecoder->do_mute(audioDecoder->Muted, false);
}
bool cPlayback::Start(char *filename, unsigned short vp, int vtype, unsigned short ap, int _ac3, unsigned int)
{
struct stat s;
off_t r;
vpid = vp;
apid = ap;
ac3 = _ac3;
hal_info("%s name = '%s' vpid 0x%04hx vtype %d apid 0x%04hx ac3 %d filelist.size: %u\n",
__FUNCTION__, filename, vpid, vtype, apid, ac3, filelist.size());
if (!filelist.empty())
{
hal_info("filelist not empty?\n");
return false;
}
if (stat(filename, &s))
{
hal_info("filename does not exist? (%m)\n");
return false;
}
if (!inbuf)
inbuf = (uint8_t *)malloc(INBUF_SIZE); /* 256 k */
if (!inbuf)
{
hal_info("allocating input buffer failed (%m)\n");
return false;
}
if (!pesbuf)
pesbuf = (uint8_t *)malloc(PESBUF_SIZE); /* 128 k */
if (!pesbuf)
{
hal_info("allocating PES buffer failed (%m)\n");
return false;
}
filelist_t file;
file.Name = std::string(filename);
file.Size = s.st_size;
if (file.Name.rfind(".ts") == file.Name.length() - 3 ||
file.Name.rfind(".TS") == file.Name.length() - 3)
filetype = FILETYPE_TS;
else
{
if (file.Name.rfind(".vdr") == file.Name.length() - 4)
{
filetype = FILETYPE_VDR;
std::string::size_type p = file.Name.rfind("info.vdr");
if (p == std::string::npos)
p = file.Name.rfind("index.vdr");
if (p != std::string::npos)
{
file.Name.replace(p, std::string::npos, "001.vdr");
hal_info("replaced filename with '%s'\n", file.Name.c_str());
if (stat(file.Name.c_str(), &s))
{
hal_info("filename does not exist? (%m)\n");
return false;
}
file.Size = s.st_size;
}
}
else
filetype = FILETYPE_MPG;
vpid = 0x40;
}
hal_info("detected (ok, guessed) filetype: %s\n", FILETYPE[filetype]);
filelist.push_back(file);
filelist_auto_add();
if (mf_open(0) < 0)
return false;
pts_start = pts_end = pts_curr = -1;
pesbuf_pos = 0;
curr_pos = 0;
inbuf_pos = 0;
inbuf_sync = 0;
r = mf_getsize();
if (r > INBUF_SIZE)
{
if (mp_seekSync(r - INBUF_SIZE) < 0)
return false;
while(true) {
if (inbuf_read() <= 0)
break; // EOF
if (curr_pos >= r) //just to make sure...
break;
}
if (filetype == FILETYPE_TS)
for (r = (inbuf_pos / 188) * 188; r > 0; r -= 188)
{
pts_end = get_pts(inbuf + r, false, inbuf_pos - r);
if (pts_end > -1)
break;
}
else
pts_end = pts_curr;
}
else
pts_end = -1; /* unknown */
if (mp_seekSync(0) < 0)
return false;
pesbuf_pos = 0;
inbuf_pos = 0;
inbuf_sync = 0;
while (inbuf_pos < INBUF_SIZE / 2 && inbuf_read() > 0) {};
for (r = 0; r < inbuf_pos - 188; r += 188)
{
pts_start = get_pts(inbuf + r, false, inbuf_pos - r);
if (pts_start > -1)
break;
}
pts_curr = pts_start;
bytes_per_second = -1;
if (pts_end != -1 && pts_start > pts_end) /* PTS overflow during this file */
pts_end += 0x200000000ULL;
int duration = (pts_end - pts_start) / 90000;
if (duration > 0)
bytes_per_second = mf_getsize() / duration;
hal_info("start: %lld end %lld duration %d bps %lld\n", pts_start, pts_end, duration, bytes_per_second);
/* yes, we start in pause mode... */
playback_speed = 0;
if (pts_start == -1)
playstate = STATE_INIT;
else
playstate = STATE_PAUSE;
pthread_mutex_lock(&playback_ready_mutex);
if (pthread_create(&thread, 0, start_playthread, this) != 0)
hal_info("pthread_create failed\n");
else
pthread_cond_wait(&playback_ready_cond, &playback_ready_mutex);
pthread_mutex_unlock(&playback_ready_mutex);
return true;
}
static void *start_playthread(void *c)
{
cPlayback *obj = (cPlayback *)c;
obj->playthread();
return NULL;
}
void cPlayback::playthread(void)
{
thread_started = true;
int ret, towrite;
dvrfd = open(DVR, O_WRONLY);
if (dvrfd < 0)
{
hal_info("%s open tdpvr failed: %m\n", __FUNCTION__);
pthread_exit(NULL);
}
fcntl(dvrfd, F_SETFD, FD_CLOEXEC);
pthread_cleanup_push(playthread_cleanup_handler, 0);
ioctl(audioDemux->getFD(), DEMUX_SELECT_SOURCE, INPUT_FROM_PVR);
if (ac3)
audioDecoder->SetStreamType(AUDIO_FMT_DOLBY_DIGITAL);
else
{
if (streamtype == 1) /* mpeg 1 */
audioDecoder->SetStreamType(AUDIO_FMT_MPG1);
else /* default */
audioDecoder->SetStreamType(AUDIO_FMT_MPEG);
}
audioDemux->pesFilter(apid);
videoDemux->pesFilter(vpid);
// audioDemux->Start();
videoDemux->Start();
// videoDecoder->setBlank(1);
// videoDecoder->Start();
// audioDecoder->Start();
/* everything is set up now, signal ::Start() that it can return */
pthread_mutex_lock(&playback_ready_mutex);
pthread_cond_broadcast(&playback_ready_cond);
pthread_mutex_unlock(&playback_ready_mutex);
while (playstate != STATE_STOP)
{
if (playstate == STATE_INIT)
{
/* hack for timeshift to determine start PTS */
pthread_mutex_lock(&inbufpos_mutex);
ret = inbuf_read();
pthread_mutex_unlock(&inbufpos_mutex);
if (ret < 0)
break;
usleep(100000);
if (pts_start == -1)
continue;
}
if (playback_speed == 0)
{
playstate = STATE_PAUSE;
usleep(1);
continue;
}
pthread_mutex_lock(&inbufpos_mutex);
ret = inbuf_read();
pthread_mutex_unlock(&inbufpos_mutex);
if (ret < 0)
break;
/* autoselect PID for PLAYMODE_FILE */
if (apid == 0 && astreams.size() > 0)
{
for (std::map<uint16_t, AStream>::iterator aI = astreams.begin(); aI != astreams.end(); aI++)
{
if (!aI->second.ac3)
{
apid = aI->first;
hal_info("%s setting Audio pid to 0x%04hx\n", __FUNCTION__, apid);
SetAPid(apid, 0);
break;
}
}
}
pthread_mutex_lock(&inbufpos_mutex);
towrite = inbuf_pos / 188 * 188; /* TODO: smaller chunks? */
if (towrite == 0)
{
pthread_mutex_unlock(&inbufpos_mutex);
continue;
}
retry:
ret = write(dvrfd, inbuf, towrite);
if (ret < 0)
{
if (errno == EAGAIN && playstate != STATE_STOP)
goto retry;
hal_info("%s write dvr failed: %m\n", __FUNCTION__);
break;
}
memmove(inbuf, inbuf + ret, inbuf_pos - ret);
inbuf_pos -= ret;
pthread_mutex_unlock(&inbufpos_mutex);
}
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
static void playthread_cleanup_handler(void *)
{
hal_info_c("%s\n", __FUNCTION__);
ioctl(audioDemux->getFD(), DEMUX_SELECT_SOURCE, INPUT_FROM_CHANNEL0);
audioDemux->Stop();
videoDemux->Stop();
audioDecoder->Stop();
videoDecoder->Stop();
close(dvrfd);
dvrfd = -1;
}
bool cPlayback::SetAPid(unsigned short pid, int _ac3)
{
hal_info("%s pid: 0x%04hx ac3: %d\n", __FUNCTION__, pid, _ac3);
apid = pid;
ac3 = _ac3;
audioDemux->Stop();
audioDecoder->Stop();
videoDemux->Stop();
videoDecoder->Stop(false);
if (ac3)
audioDecoder->SetStreamType(AUDIO_FMT_DOLBY_DIGITAL);
else
{
if (streamtype == 1) /* mpeg 1 */
audioDecoder->SetStreamType(AUDIO_FMT_MPG1);
else /* default */
audioDecoder->SetStreamType(AUDIO_FMT_MPEG);
}
audioDemux->pesFilter(apid);
videoDemux->Start();
audioDemux->Start();
audioDecoder->Start();
videoDecoder->Start();
return true;
}
bool cPlayback::SetSpeed(int speed)
{
hal_info("%s speed = %d\n", __FUNCTION__, speed);
if (speed < 0)
speed = 1; /* fast rewind not yet implemented... */
if (speed == 1 && playback_speed != 1)
{
if (playback_speed == 0)
{
videoDemux->Stop();
videoDemux->Start();
audioDemux->Start();
}
else
{
audioDecoder->Stop();
videoDecoder->Stop();
}
audioDecoder->Start();
videoDecoder->Start();
playstate = STATE_PLAY;
/* cPlayback is a friend of cAudio and can use private methods */
audioDecoder->do_mute(audioDecoder->Muted, false);
}
if (playback_speed == 1 && speed > 1)
{
audioDecoder->mute(false);
videoDecoder->FastForwardMode();
}
playback_speed = speed;
if (playback_speed == 0)
{
audioDecoder->Stop();
audioDemux->Stop();
videoDecoder->Stop(false);
}
return true;
}
bool cPlayback::GetSpeed(int &speed) const
{
hal_debug("%s\n", __FUNCTION__);
speed = playback_speed;
return true;
}
// in milliseconds
bool cPlayback::GetPosition(int &position, int &duration)
{
int64_t tmppts;
hal_debug("%s\n", __FUNCTION__);
off_t currsize = mf_getsize();
bool update = false;
/* handle a growing file, e.g. for timeshift.
this might be pretty expensive... */
if (filetype == FILETYPE_TS && filelist.size() == 1)
{
off_t tmppos = currsize - PESBUF_SIZE;
if (currsize > last_size && (currsize - last_size) < 10485760 &&
bytes_per_second > 0 && _pts_end > 0)
{
/* guess the current endpts... */
tmppts = (currsize - last_size) * 90000 / bytes_per_second;
pts_end = _pts_end + tmppts;
}
else if (currsize != last_size && tmppos > 0)
{
pthread_mutex_lock(&currpos_mutex);
off_t oldpos = curr_pos;
ssize_t n, r;
int s;
mf_lseek(tmppos);
n = read(in_fd, pesbuf, PESBUF_SIZE); /* abuse the pesbuf... */
s = sync_ts(pesbuf, n);
if (s >= 0)
{
n -= s;
for (r = (n / 188) * 188; r > 0; r -= 188)
{
tmppts = get_pts(pesbuf + r + s, false, n - r);
if (tmppts > -1)
{
hal_debug("n: %d s: %d endpts %lld size: %lld\n", n, s, tmppts, currsize);
pts_end = tmppts;
_pts_end = tmppts;
update = true;
/* file size has changed => update endpts */
last_size = currsize;
break;
}
}
}
mf_lseek(oldpos);
pthread_mutex_unlock(&currpos_mutex);
}
}
if (pts_end != -1 && pts_start > pts_end) /* should trigger only once ;) */
{
pts_end += 0x200000000ULL;
update = true;
}
if (pts_curr != -1 && pts_curr < pts_start)
tmppts = pts_curr + 0x200000000ULL - pts_start;
else
tmppts = pts_curr - pts_start;
if (pts_end != -1 && pts_curr != -1)
{
position = tmppts / 90;
duration = (pts_end - pts_start) / 90;
if (update && duration >= 4000)
{
bytes_per_second = currsize / (duration / 1000);
hal_debug("%s: updated bps: %lld size: %lld duration %d\n",
__FUNCTION__, bytes_per_second, currsize, duration);
}
return true;
}
position = 0;
duration = 0;
return false;
}
bool cPlayback::SetPosition(int position, bool absolute)
{
hal_info("%s pos = %d abs = %d\n", __FUNCTION__, position, absolute);
int currpos, target, duration, oldspeed;
bool ret;
if (absolute)
target = position;
else
{
GetPosition(currpos, duration);
target = currpos + position;
hal_info("current position %d target %d\n", currpos, target);
}
oldspeed = playback_speed;
// if (oldspeed != 0)
SetSpeed(0); /* request pause */
while (playstate == STATE_PLAY) /* playthread did not acknowledge pause */
usleep(1);
if (playstate == STATE_STOP) /* we did get stopped by someone else */
return false;
ret = (seek_to_pts(target * 90) > 0);
if (oldspeed != 0)
{
SetSpeed(oldspeed);
/* avoid ugly artifacts */
videoDecoder->Stop();
videoDecoder->Start();
}
return ret;
}
void cPlayback::FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language)
{
hal_info("%s\n", __FUNCTION__);
int i = 0;
for (std::map<uint16_t, AStream>::iterator aI = astreams.begin(); aI != astreams.end(); aI++)
{
apids[i] = aI->first;
ac3flags[i] = aI->second.ac3 ? 1 : 0;
language[i] = aI->second.lang;
i++;
if (i > 10) /* REC_MAX_APIDS in vcrcontrol.h */
break;
}
*numpida = i;
}
/* it is unlikely that subtitle support will be implemented soon */
void cPlayback::FindAllSubs(uint16_t *, unsigned short *, uint16_t *num, std::string *)
{
*num = 0;
}
bool cPlayback::SelectSubtitles(int)
{
return false;
}
/* DVD support is also unlikely... */
void cPlayback::GetChapters(std::vector<int> &positions, std::vector<std::string> &titles)
{
positions.clear();
titles.clear();
}
off_t cPlayback::seek_to_pts(int64_t pts)
{
off_t newpos = curr_pos;
int64_t tmppts, ptsdiff;
int count = 0;
if (pts_start < 0 || pts_end < 0 || bytes_per_second < 0)
{
hal_info("%s pts_start (%lld) or pts_end (%lld) or bytes_per_second (%lld) not initialized\n",
__FUNCTION__, pts_start, pts_end, bytes_per_second);
return -1;
}
/* sanity check: buffer is without locking, so we must only seek while in pause mode */
if (playstate != STATE_PAUSE)
{
hal_info("%s playstate (%d) != STATE_PAUSE, not seeking\n", __FUNCTION__, playstate);
return -1;
}
/* tmppts is normalized current pts */
if (pts_curr < pts_start)
tmppts = pts_curr + 0x200000000ULL - pts_start;
else
tmppts = pts_curr - pts_start;
while (abs(pts - tmppts) > 90000LL && count < 10)
{
count++;
ptsdiff = pts - tmppts;
newpos += ptsdiff * bytes_per_second / 90000;
hal_info("%s try #%d seek from %lldms to %lldms dt %lldms pos %lldk newpos %lldk kB/s %lld\n",
__FUNCTION__, count, tmppts / 90, pts / 90, ptsdiff / 90, curr_pos / 1024, newpos / 1024, bytes_per_second / 1024);
if (newpos < 0)
newpos = 0;
newpos = mp_seekSync(newpos);
if (newpos < 0)
return newpos;
pthread_mutex_lock(&inbufpos_mutex);
inbuf_pos = 0;
inbuf_sync = 0;
while (inbuf_pos < INBUF_SIZE * 8 / 10) {
if (inbuf_read() <= 0)
break; // EOF
}
pthread_mutex_unlock(&inbufpos_mutex);
if (pts_curr < pts_start)
tmppts = pts_curr + 0x200000000ULL - pts_start;
else
tmppts = pts_curr - pts_start;
}
hal_info("%s end after %d tries, ptsdiff now %lld sec\n", __FUNCTION__, count, (pts - tmppts) / 90000);
return newpos;
}
bool cPlayback::filelist_auto_add()
{
if (filelist.size() != 1)
return false;
const char *filename = filelist[0].Name.c_str();
const char *ext;
ext = strrchr(filename, '.'); // FOO-xxx-2007-12-31.001.ts <- the dot before "ts"
// 001.vdr <- the dot before "vdr"
// check if there is something to do...
if (! ext)
return false;
if (!((ext - 7 >= filename && !strcmp(ext, ".ts") && *(ext - 4) == '.') ||
(ext - 4 >= filename && !strcmp(ext, ".vdr"))))
return false;
int num = 0;
struct stat s;
size_t numpos = strlen(filename) - strlen(ext) - 3;
sscanf(filename + numpos, "%d", &num);
do {
num++;
char nextfile[strlen(filename) + 1]; /* todo: use fixed buffer? */
memcpy(nextfile, filename, numpos);
sprintf(nextfile + numpos, "%03d%s", num, ext);
if (stat(nextfile, &s))
break; // file does not exist
filelist_t file;
file.Name = std::string(nextfile);
file.Size = s.st_size;
hal_info("%s auto-adding '%s' to playlist\n", __FUNCTION__, nextfile);
filelist.push_back(file);
} while (true && num < 999);
return (filelist.size() > 1);
}
/* the mf_* functions are wrappers for multiple-file I/O */
int cPlayback::mf_open(int fileno)
{
if (filelist.empty())
return -1;
if (fileno >= (int)filelist.size())
return -1;
mf_close();
in_fd = open(filelist[fileno].Name.c_str(), O_RDONLY);
fcntl(in_fd, F_SETFD, FD_CLOEXEC);
if (in_fd != -1)
curr_fileno = fileno;
return in_fd;
}
int cPlayback::mf_close(void)
{
int ret = 0;
hal_info("%s in_fd = %d curr_fileno = %d\n", __FUNCTION__, in_fd, curr_fileno);
if (in_fd != -1)
ret = close(in_fd);
in_fd = curr_fileno = -1;
return ret;
}
off_t cPlayback::mf_getsize(void)
{
off_t ret = 0;
if (filelist.size() == 1 && in_fd != -1)
{
/* for timeshift, we need to deal with a growing file... */
struct stat st;
if (fstat(in_fd, &st) == 0)
return st.st_size;
/* else, fallback to filelist.size() */
}
for (unsigned int i = 0; i < filelist.size(); i++)
ret += filelist[i].Size;
return ret;
}
off_t cPlayback::mf_lseek(off_t pos)
{
off_t offset = 0, lpos = pos, ret;
unsigned int fileno;
/* this is basically needed for timeshifting - to allow
growing files to be handled... */
if (filelist.size() == 1 && filetype == FILETYPE_TS)
{
if (lpos > mf_getsize())
return -2;
fileno = 0;
}
else
{
for (fileno = 0; fileno < filelist.size(); fileno++)
{
if (lpos < filelist[fileno].Size)
break;
offset += filelist[fileno].Size;
lpos -= filelist[fileno].Size;
}
if (fileno == filelist.size())
return -2; // EOF
}
if ((int)fileno != curr_fileno)
{
hal_info("%s old fileno: %d new fileno: %d, offset: %lld\n", __FUNCTION__, curr_fileno, fileno, (long long)lpos);
in_fd = mf_open(fileno);
if (in_fd < 0)
{
hal_info("cannot open file %d:%s (%m)\n", fileno, filelist[fileno].Name.c_str());
return -1;
}
}
ret = lseek(in_fd, lpos, SEEK_SET);
if (ret < 0)
return ret;
curr_pos = offset + ret;
return curr_pos;
}
/* gets the PTS at a specific file position from a PES
ATTENTION! resets buf! */
int64_t cPlayback::get_PES_PTS(uint8_t *buf, int len, bool last)
{
int64_t pts = -1;
int off, plen;
uint8_t *p;
off = mp_syncPES(buf, len);
if (off < 0)
return off;
p = buf + off;
while (off < len - 14 && (pts == -1 || last))
{
plen = ((p[4] << 8) | p[5]) + 6;
switch(p[3])
{
int64_t tmppts;
case 0xe0 ... 0xef: // video!
tmppts = get_pts(p, true, len - off);
if (tmppts >= 0)
pts = tmppts;
break;
case 0xbb:
case 0xbe:
case 0xbf:
case 0xf0 ... 0xf3:
case 0xff:
case 0xc0 ... 0xcf:
case 0xd0 ... 0xdf:
break;
case 0xb9:
case 0xba:
case 0xbc:
default:
plen = 1;
break;
}
p += plen;
off += plen;
}
return pts;
}
/* needs to be called with inbufpos_mutex locked! */
ssize_t cPlayback::inbuf_read()
{
if (filetype == FILETYPE_UNKNOWN)
return -1;
if (filetype == FILETYPE_TS)
return read_ts();
/* FILETYPE_MPG or FILETYPE_VDR */
return read_mpeg();
}
ssize_t cPlayback::read_ts()
{
ssize_t toread, ret = 0, sync, off;
toread = INBUF_SIZE - inbuf_pos;
bool retry = true;
uint8_t *buf;
/* fprintf(stderr, "%s:%d curr_pos %lld, inbuf_pos: %ld, toread: %ld\n",
__FUNCTION__, __LINE__, (long long)curr_pos, (long)inbuf_pos, (long)toread); */
if (playback_speed > 1)
{
sync = 0;
ssize_t tmpread = PESBUF_SIZE / 188 * 188;
int n, skipped = 0;
bool skip = false;
bool eof = true;
pthread_mutex_lock(&currpos_mutex);
while (toread > 0)
{
ssize_t done = 0;
while (done < tmpread)
{
ret = read(in_fd, pesbuf, tmpread - done);
if (ret == 0 && retry) /* EOF */
{
mf_lseek(curr_pos);
retry = false;
continue;
}
if (ret < 0)
{
hal_info("%s failed1: %m\n", __FUNCTION__);
pthread_mutex_unlock(&currpos_mutex);
return ret;
}
if (ret == 0 && eof)
goto out;
eof = false;
done += ret;
curr_pos += ret;
}
sync = sync_ts(pesbuf, ret);
if (sync != 0)
{
hal_info("%s out of sync: %d\n", __FUNCTION__, sync);
if (sync < 0)
{
pthread_mutex_unlock(&currpos_mutex);
return -1;
}
memmove(pesbuf, pesbuf + sync, ret - sync);
if (pesbuf[0] != 0x47)
hal_info("%s:%d??????????????????????????????\n", __FUNCTION__, __LINE__);
}
for (n = 0; n < done / 188 * 188; n += 188)
{
buf = pesbuf + n;
if (buf[1] & 0x40) // PUSI
{
/* only video packets... */
int of = 4;
if (buf[3] & 0x20) // adaptation field
of += buf[4] + 1;
if ((buf[of + 3] & 0xF0) == 0xE0 && // Video stream
buf[of + 2] == 0x01 && buf[of + 1] == 0x00 && buf[of] == 0x00) // PES
{
skip = true;
skipped++;
if (skipped >= playback_speed)
{
skipped = 0;
skip = false;
}
}
}
if (! skip)
{
memcpy(inbuf + inbuf_pos, buf, 188);
inbuf_pos += 188;
toread -= 188;
if (toread <= 0)
{
/* the output buffer is full, discard the input :-( */
if (done - n > 0)
{
hal_debug("%s not done: %d, resetting filepos\n",
__FUNCTION__, done - n);
mf_lseek(curr_pos - (done - n));
}
break;
}
}
}
}
out:
pthread_mutex_unlock(&currpos_mutex);
if (eof)
return 0;
}
else
{
pthread_mutex_lock(&currpos_mutex);
while(true)
{
ret = read(in_fd, inbuf + inbuf_pos, toread);
if (ret == 0 && retry) /* EOF */
{
mf_lseek(curr_pos);
retry = false;
continue;
}
break;
}
if (ret <= 0)
{
pthread_mutex_unlock(&currpos_mutex);
if (ret < 0)
hal_info("%s failed2: %m\n", __FUNCTION__);
return ret;
}
inbuf_pos += ret;
curr_pos += ret;
pthread_mutex_unlock(&currpos_mutex);
sync = sync_ts(inbuf + inbuf_sync, INBUF_SIZE - inbuf_sync);
if (sync < 0)
{
hal_info("%s cannot sync\n", __FUNCTION__);
return ret;
}
inbuf_sync += sync;
}
/* check for A/V PIDs */
uint16_t pid;
int i;
int64_t pts;
//fprintf(stderr, "inbuf_pos: %ld - sync: %ld, inbuf_syc: %ld\n", (long)inbuf_pos, (long)sync, (long)inbuf_sync);
int synccnt = 0;
for (i = 0; i < inbuf_pos - inbuf_sync - 13;) {
buf = inbuf + inbuf_sync + i;
if (*buf != 0x47)
{
synccnt++;
i++;
continue;
}
if (synccnt)
hal_info("%s TS went out of sync %d\n", __FUNCTION__, synccnt);
synccnt = 0;
if (!(buf[1] & 0x40)) /* PUSI */
{
i += 188;
continue;
}
off = 0;
if (buf[3] & 0x20) /* adaptation field? */
off = buf[4] + 1;
pid = get_pid(buf + 1);
/* PES signature is at buf + 4, streamtype is after 00 00 01 */
switch (buf[4 + 3 + off])
{
case 0xe0 ... 0xef: /* video stream */
if (vpid == 0)
vpid = pid;
pts = get_pts(buf + 4 + off, true, inbuf_pos - inbuf_sync - i - off - 4);
if (pts < 0)
break;
pts_curr = pts;
if (pts_start < 0)
{
hal_info("%s updating pts_start to %lld ", __FUNCTION__, pts);
pts_start = pts;
if (pts_end > -1)
{
if (pts_end < pts_start)
{
pts_end += 0x200000000ULL;
fprintf(stderr, "pts_end to %lld ", pts_end);
}
int duration = (pts_end - pts_start) / 90000;
if (duration > 0)
{
bytes_per_second = (mf_getsize() - curr_pos) / duration;
fprintf(stderr, "bytes_per_second to %lldk duration to %ds at %lldk",
bytes_per_second / 1024, duration, curr_pos / 1024);
}
}
fprintf(stderr, "\n");
}
break;
case 0xbd: /* private stream 1 - ac3 */
case 0xc0 ... 0xdf: /* audio stream */
if (astreams.find(pid) != astreams.end())
break;
AStream tmp;
if (buf[7 + off] == 0xbd)
{
if (buf[12 + off] == 0x24) /* 0x24 == TTX */
break;
tmp.ac3 = true;
}
else
tmp.ac3 = false;
tmp.lang = "";
astreams.insert(std::make_pair(pid, tmp));
hal_info("%s found apid #%d 0x%04hx ac3:%d\n", __func__, astreams.size(), pid, tmp.ac3);
break;
}
i += 188;
}
// fprintf(stderr, "%s:%d ret %ld\n", __FUNCTION__, __LINE__, (long long)ret);
return ret;
}
ssize_t cPlayback::read_mpeg()
{
ssize_t toread, ret, sync;
//toread = PESBUF_SIZE - pesbuf_pos;
/* experiments found, that 80kB is the best buffer size, otherwise a/v sync seems
to suffer and / or audio stutters */
toread = 80 * 1024 - pesbuf_pos;
bool retry = true;
if (INBUF_SIZE - inbuf_pos < toread)
{
hal_info("%s inbuf full, setting toread to %d (old: %zd)\n", __FUNCTION__, INBUF_SIZE - inbuf_pos, toread);
toread = INBUF_SIZE - inbuf_pos;
}
pthread_mutex_lock(&currpos_mutex);
while(true)
{
ret = read(in_fd, pesbuf + pesbuf_pos, toread);
if (ret == 0 && retry) /* EOF */
{
mf_lseek(curr_pos);
retry = false;
continue;
}
break;
}
if (ret < 0)
{
pthread_mutex_unlock(&currpos_mutex);
hal_info("%s failed: %m, pesbuf_pos: %zd, toread: %zd\n", __FUNCTION__, pesbuf_pos, toread);
return ret;
}
pesbuf_pos += ret;
curr_pos += ret;
pthread_mutex_unlock(&currpos_mutex);
int count = 0;
uint16_t pid = 0;
bool resync = true;
while (count < pesbuf_pos - 10)
{
if (resync)
{
sync = mp_syncPES(pesbuf + count, pesbuf_pos - count - 10);
if (sync < 0)
{
if (pesbuf_pos - count - 10 > 4)
hal_info("%s cannot sync (count=%d, pesbuf_pos=%zd)\n",
__FUNCTION__, count, pesbuf_pos);
break;
}
if (sync)
hal_info("%s needed sync %zd\n", __FUNCTION__, sync);
count += sync;
}
uint8_t *ppes = pesbuf + count;
int av = 0; // 1 = video, 2 = audio
int64_t pts;
switch(ppes[3])
{
case 0xba: //pack header;
// fprintf(stderr, "pack start code, 0x%02x\n", ppes[4]);
if ((ppes[4] & 0xf0) == 0x20) /* mpeg 1 */
{
streamtype = 1; /* for audio setup */
count += 12;
}
else if ((ppes[4] & 0xc0) == 0x40) /* mpeg 2 */
{
streamtype = 0;
count += 14; /* correct: 14 + (ppes[13] & 0x07) */
}
else
{
hal_info("%s weird pack header: 0x%2x\n", __FUNCTION__, ppes[4]);
count++;
}
resync = true;
continue;
break;
case 0xbd: // AC3
{
int off = ppes[8] + 8 + 1; // ppes[8] is often 0
if (count + off >= pesbuf_pos)
break;
uint16_t subid = ppes[off];
// if (offset == 0x24 && subid == 0x10 ) // TTX?
if (subid < 0x80 || subid > 0x87)
break;
hal_debug("AC3: ofs 0x%02x subid 0x%02x\n", off, subid);
//subid -= 0x60; // normalize to 32...39 (hex 0x20..0x27)
if (astreams.find(subid) == astreams.end())
{
AStream tmp;
tmp.ac3 = true;
tmp.lang = "";
astreams.insert(std::make_pair(subid, tmp));
hal_info("%s found aid: %02x\n", __FUNCTION__, subid);
}
pid = subid;
av = 2;
break;
}
case 0xbb:
case 0xbe:
case 0xbf:
case 0xf0 ... 0xf3:
case 0xff:
//skip = (ppes[4] << 8 | ppes[5]) + 6;
//DBG("0x%02x header, skip = %d\n", ppes[3], skip);
break;
case 0xc0 ... 0xcf:
case 0xd0 ... 0xdf:
{
// fprintf(stderr, "audio stream 0x%02x\n", ppes[3]);
uint16_t id = ppes[3];
if (astreams.find(id) == astreams.end())
{
AStream tmp;
tmp.ac3 = false;
tmp.lang = "";
astreams.insert(std::make_pair(id, tmp));
hal_info("%s found aid: %02x\n", __FUNCTION__, id);
}
pid = id;
av = 2;
break;
}
case 0xe0 ... 0xef:
// fprintf(stderr, "video stream 0x%02x, %02x %02x \n", ppes[3], ppes[4], ppes[5]);
pid = 0x40;
av = 1;
pts = get_pts(ppes, true, pesbuf_pos - count);
if (pts < 0)
break;
pts_curr = pts;
if (pts_start < 0)
pts_start = pts;
break;
case 0xb9:
case 0xbc:
hal_debug("%s:%d %s\n", __FUNCTION__, __LINE__,
(ppes[3] == 0xb9) ? "program_end_code" : "program_stream_map");
//resync = true;
// fallthrough. TODO: implement properly.
default:
//if (! resync)
// DBG("Unknown stream id: 0x%X.\n", ppes[3]);
count++;
resync = true;
continue;
break;
}
int pesPacketLen = ((ppes[4] << 8) | ppes[5]) + 6;
if (count + pesPacketLen >= pesbuf_pos)
{
hal_debug("buffer len: %ld, pesPacketLen: %d :-(\n", pesbuf_pos - count, pesPacketLen);
break;
}
int tsPacksCount = pesPacketLen / 184;
if ((tsPacksCount + 1) * 188 > INBUF_SIZE - inbuf_pos)
{
hal_info("not enough size in inbuf (needed %d, got %d)\n", (tsPacksCount + 1) * 188, INBUF_SIZE - inbuf_pos);
break;
}
if (av)
{
int rest = pesPacketLen % 184;
// divide PES packet into small TS packets
uint8_t pusi = 0x40;
int j;
uint8_t *ts = inbuf + inbuf_pos;
for (j = 0; j < tsPacksCount; j++)
{
ts[0] = 0x47; // SYNC Byte
ts[1] = pusi; // Set PUSI if first packet
ts[2] = pid; // PID (low)
ts[3] = 0x10 | (cc[pid] & 0x0F); // No adaptation field, payload only, continuity counter
cc[pid]++;
memcpy(ts + 4, ppes + j * 184, 184);
pusi = 0x00; // clear PUSI
ts += 188;
inbuf_pos += 188;
}
if (rest > 0)
{
ts[0] = 0x47; // SYNC Byte
ts[1] = pusi; // Set PUSI or
ts[2] = pid; // PID (low)
ts[3] = 0x30 | (cc[pid] & 0x0F); // adaptation field, payload, continuity counter
cc[pid]++;
ts[4] = 183 - rest;
if (ts[4] > 0)
{
ts[5] = 0x00;
memset(ts + 6, 0xFF, ts[4] - 1);
}
memcpy(ts + 188 - rest, ppes + j * 184, rest);
inbuf_pos += 188;
}
} //if (av)
count += pesPacketLen;
}
memmove(pesbuf, pesbuf + count, pesbuf_pos - count);
pesbuf_pos -= count;
return ret;
}
//== seek to pos with sync to next proper TS packet ==
//== returns offset to start of TS packet or actual ==
//== pos on failure. ==
//====================================================
off_t cPlayback::mp_seekSync(off_t pos)
{
off_t npos = pos;
off_t ret;
uint8_t pkt[1024];
pthread_mutex_lock(&currpos_mutex);
ret = mf_lseek(npos);
if (ret < 0)
hal_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__);
if (filetype != FILETYPE_TS)
{
int offset = 0;
int s;
ssize_t r;
bool retry = false;
while (true)
{
r = read(in_fd, &pkt[offset], 1024 - offset);
if (r < 0)
{
hal_info("%s read failed: %m\n", __FUNCTION__);
break;
}
if (r == 0) // EOF?
{
if (retry)
break;
if (mf_lseek(npos) < 0) /* next file in list? */
{
hal_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__);
break;
}
retry = true;
continue;
}
s = mp_syncPES(pkt, r + offset, true);
if (s < 0)
{
/* if the last 3 bytes of the buffer were 00 00 01, then
mp_sync_PES would not find it. So keep them and check
again in the next iteration */
memmove(pkt, &pkt[r + offset - 3], 3);
npos += r;
offset = 3;
}
else
{
npos += s;
hal_info("%s sync after %lld\n", __FUNCTION__, npos - pos);
ret = mf_lseek(npos);
pthread_mutex_unlock(&currpos_mutex);
if (ret < 0)
hal_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__);
return ret;
}
if (npos > (pos + 0x20000)) /* 128k enough? */
break;
}
hal_info("%s could not sync to PES offset: %d r: %zd\n", __FUNCTION__, offset, r);
ret = mf_lseek(pos);
pthread_mutex_unlock(&currpos_mutex);
return ret;
}
/* TODO: use bigger buffer here, too and handle EOF / next splitfile */
while (read(in_fd, pkt, 1) > 0)
{
//-- check every byte until sync word reached --
npos++;
if (*pkt == 0x47)
{
//-- if found double check for next sync word --
if (read(in_fd, pkt, 188) == 188)
{
if(pkt[188-1] == 0x47)
{
ret = mf_lseek(npos - 1); // assume sync ok
pthread_mutex_unlock(&currpos_mutex);
if (ret < 0)
hal_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__);
return ret;
}
else
{
ret = mf_lseek(npos); // oops, next pkt doesn't start with sync
if (ret < 0)
hal_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__);
}
}
}
//-- check probe limits --
if (npos > (pos + 100 * 188))
break;
}
//-- on error stay on actual position --
ret = mf_lseek(pos);
pthread_mutex_unlock(&currpos_mutex);
return ret;
}
static int sync_ts(uint8_t *p, int len)
{
int count;
if (len < 189)
return -1;
count = 0;
while (*p != 0x47 || *(p + 188) != 0x47)
{
count++;
p++;
if (count + 188 > len)
return -1;
}
return count;
}
/* get the pts value from a TS or PES packet
pes == true selects PES mode. */
int64_t cPlayback::get_pts(uint8_t *p, bool pes, int bufsize)
{
const uint8_t *end = p + bufsize; /* check for overflow */
if (bufsize < 14)
return -1;
if (!pes)
{
if (p[0] != 0x47)
return -1;
if (!(p[1] & 0x40))
return -1;
if (get_pid(p + 1) != vpid)
return -1;
if (!(p[3] & 0x10))
return -1;
if (p[3] & 0x20)
p += p[4] + 4 + 1;
else
p += 4;
if (p + 13 > end)
return -1;
/* p is now pointing at the PES header. hopefully */
if (p[0] || p[1] || (p[2] != 1))
return -1;
}
if ((p[6] & 0xC0) != 0x80) // MPEG1
{
p += 6;
while (*p == 0xff)
{
p++;
if (p > end)
return -1;
}
if ((*p & 0xc0) == 0x40)
p += 2;
p -= 9; /* so that the p[9]...p[13] matches the below */
if (p + 13 > end)
return -1;
}
else
{
/* MPEG2 */
if ((p[7] & 0x80) == 0) // packets with both pts, don't care for dts
// if ((p[7] & 0xC0) != 0x80) // packets with only pts
// if ((p[7] & 0xC0) != 0xC0) // packets with pts and dts
return -1;
if (p[8] < 5)
return -1;
}
if (!(p[9] & 0x20))
return -1;
int64_t pts =
((p[ 9] & 0x0EULL) << 29) |
((p[10] & 0xFFULL) << 22) |
((p[11] & 0xFEULL) << 14) |
((p[12] & 0xFFULL) << 7) |
((p[13] & 0xFEULL) >> 1);
//int msec = pts / 90;
//INFO("time: %02d:%02d:%02d\n", msec / 3600000, (msec / 60000) % 60, (msec / 1000) % 60);
return pts;
}
/* returns: 0 == was already synchronous, > 0 == is now synchronous, -1 == could not sync */
static int mp_syncPES(uint8_t *buf, int len, bool quiet)
{
int ret = 0;
while (ret < len - 4)
{
if (buf[ret + 2] != 0x01)
{
ret++;
continue;
}
if (buf[ret + 1] != 0x00)
{
ret += 2;
continue;
}
if (buf[ret] != 0x00)
{
ret += 3;
continue;
}
/* all stream IDs are > 0x80 */
if ((buf[ret + 3] & 0x80) != 0x80)
{
/* we already checked for 00 00 01, if the stream ID
is not valid, we can skip those 3 bytes */
ret += 3;
continue;
}
return ret;
}
if (!quiet && len > 5) /* only warn if enough space was available... */
hal_info_c("%s No valid PES signature found. %d Bytes deleted.\n", __FUNCTION__, ret);
return -1;
}
static inline uint16_t get_pid(uint8_t *buf)
{
return (*buf & 0x1f) << 8 | *(buf + 1);
}