sigmaplayer/src/mpg.cpp

740 lines
17 KiB
C++

//////////////////////////////////////////////////////////////////////////
/**
* SigmaPlayer source project - MPEG1/MPEG2/VOB files player source file.
* \file mpg.cpp
* \author bombur
* \version 0.2
* \date 02.07.2010 07.05.2007
*
* 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, USA.
*/
//////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <libsp/sp_misc.h>
#include <libsp/sp_msg.h>
#include <libsp/sp_khwl.h>
#include <libsp/sp_mpeg.h>
#include <libsp/sp_cdrom.h>
#include "script.h"
#include "player.h"
#include "media.h"
#include "settings.h"
#ifdef INTERNAL_VIDEO_PLAYER
#define VIDEO_INTERNAL
#include "audio.h"
#include "video.h"
#include "mpg.h"
#define MSG if (video_msg) msg
static const int seek_delta_pts = 60 * 90000; // 1 min.
VideoMpg::VideoMpg()
{
type = VIDEO_CONTAINER_MPEG;
new_frame_size = true;
cur_rate = 0;
scan_for_length = true;
seek_start = true;
start_time_pts = -1;
end_time_pts = -1;
totaltime_pts = 0;
pts_base = 0;
wait_for_new_pts = false;
set_pts = true;
elementary_stream = false;
is_cdxa = false;
packet_left = 0;
saved_sct = START_CODE_UNKNOWN;
saved_length = -1;
saved_start_counter = 0;
saved_feed = true;
//tmp_packet_header_buf_active = false;
//tmp_packet_header_buf_size = 0;
packet_header_incomplete = false;
was_pack = false;
mpeg_format = -1;
mpeg_aspect = -1;
old_pts = 0;
video_pts = 0;
is_totaltime_from_rate = false;
}
VideoMpg::~VideoMpg()
{
}
////////////////////////////////////////////////
BOOL VideoMpg::Parse()
{
// Read first 4 bytes and check that this is an MPEG file
BYTE data[16];
if (video_read(data, 12) != 12)
{
msg_error("Mpg: Read error.\n");
return FALSE;
}
if (strncasecmp((char *)data, "RIFF", 4) == 0 && strncasecmp((char *)data+8, "AVI ", 4) == 0)
{
msg_error("Mpg: Avi file found in MPEG container.\n");
return FALSE;
}
if (strncasecmp((char *)data, "RIFF", 4) == 0 && strncasecmp((char *)data+8, "CDXA", 4) == 0)
{
msg("Mpg: CDXA format detected.\n");
is_cdxa = true;
}
video_lseek(0, SEEK_END);
filesize = video_lseek(0L, SEEK_CUR);
seek_filesize = filesize;
//msg("! seek_filesize = %d\n", (int)seek_filesize);
video_lseek(0, SEEK_SET);
video_fmt = RIFF_VIDEO_MPEG12;
video_fmt_str.Printf("MPEG-1/2");
//MPEG_PACKET_LENGTH = 2048;
MPEG_PACKET_LENGTH = 32768;
MPEG_NUM_PACKETS = 512;
set_media_type(MEDIA_TYPE_MPEG);
return TRUE;
}
BOOL VideoMpg::GetTimeLength()
{
LONGLONG pos = video_lseek(-32768, SEEK_END);
int ret = mpeg_find_free_blocks(MPEG_BUFFER_1);
// we cannot wait
if (ret < 1)
return FALSE;
BYTE *base = mpeg_getcurbuf(MPEG_BUFFER_1);
if (base == NULL)
return FALSE;
int len = (int)video_read(base, (int)(filesize - pos));
if (len < 1)
return FALSE;
// now parse the stream
MPEG_PACKET_LENGTH = len;
start_time_pts = -1;
end_time_pts = -1;
totaltime_pts = 0;
seek_start = false;
scan_for_length = true;
fps = 30; // not correct, but it works
ProcessChunk(base, len);
MPEG_PACKET_LENGTH = 32768;
video_lseek(0, SEEK_SET);
seek_start = true;
scan_for_length = true;
return TRUE;
}
//////////////////////////////////////////////////////////////////
VIDEO_CHUNK_TYPE VideoMpg::GetNext(BYTE *, int , int *, int *, int *)
{
return VIDEO_CHUNK_UNKNOWN;
}
int VideoMpg::ProcessChunk(BYTE *buf, int )
{
START_CODE_TYPES sct;
BYTE *base = buf, *lastbuf = base;
for (; ;)
{
if (packet_left > 0 || (elementary_stream && saved_sct != START_CODE_UNKNOWN))
sct = saved_sct;
else
{
sct = mpeg_findpacket(buf, base, saved_start_counter);
if (sct == START_CODE_END)
{
return 1;
}
saved_start_counter = 0;
}
bool datapacket = false;
switch (sct)
{
case START_CODE_PACK:
{
int left = MPEG_PACKET_LENGTH - (buf - base);
if (packet_left > 0)
{
memcpy(tmp_packet_header_buf + packet_left, buf, 8 - packet_left);
BYTE *b = tmp_packet_header_buf;
scr = mpeg_parse_program_stream_pack_header(b, tmp_packet_header_buf);
buf += 8;
packet_left = 0;
}
else if (left < 8)
{
memcpy(tmp_packet_header_buf, buf, left);
packet_left = left;
saved_sct = sct;
return 1;
}
else
scr = mpeg_parse_program_stream_pack_header(buf, base);
scr += pts_base;
if (scr < 0)
scr = 0;
if (scr > 0 && scan_for_length)
{
//scan_for_length = false;
if (seek_start)
{
if (start_time_pts < 0)
{
start_time_pts = scr;
UpdateTotalTime();
}
}
else
{
if (scr > end_time_pts && (end_time_pts < 0 || scr < end_time_pts + 120000))
end_time_pts = scr;
// early exit
//return 1;
}
}
// this is needed for 'khwl'
scr |= SPTM_SCR_FLAG;
was_pack = true;
lastbuf = buf;
}
break;
case START_CODE_SYSTEM:
case START_CODE_PCI:
case START_CODE_PADDING:
{
int len;
int left = MPEG_PACKET_LENGTH - (buf - base);
if (left < 1)
return 1;
if (packet_left > 0)
{
// if we read only 1 byte of packet length
if (saved_length >= 0)
{
// one byte was already read
len = ((saved_length << 8) | buf[0]) + 1;
} else
len = packet_left;
packet_left = 0;
saved_length = -1;
}
else
{
if (left < 2) // cannot get length
{
saved_length = buf[0];
packet_left = 1;
saved_sct = sct;
return 1;
}
len = ((buf[0] << 8) | buf[1]) + 2;
}
if (len > left) // length is greater than packet size
{
packet_left = len - left;
saved_length = -1;
len = left;
saved_sct = sct;
return 1;
}
// it's safe now
buf += len;
lastbuf = buf;
break;
}
case START_CODE_MPEG_VIDEO_ELEMENTARY:
elementary_stream = true;
saved_sct = sct;
case START_CODE_MPEG_VIDEO:
case START_CODE_MPEG_AUDIO1:
case START_CODE_MPEG_AUDIO2:
case START_CODE_PRIVATE1:
datapacket = true;
break;
default:;
}
if (datapacket)
{
if (scan_for_length)
{
LONGLONG pts = 0;
if (!seek_start)
{
if (sct == START_CODE_MPEG_VIDEO && was_pack)
{
MpegPacket tmppacket;
tmppacket.flags = 0;
tmppacket.pts = 0;
if (mpeg_extractpacket(buf, base, &tmppacket, sct, FALSE) >= 0)
{
if (tmppacket.flags == 2 && tmppacket.pts > 0)
pts = tmppacket.pts;
}
buf += tmppacket.size;
lastbuf = buf;
}
}
#if 0
if (sct == START_CODE_MPEG_VIDEO || sct == START_CODE_MPEG_VIDEO_ELEMENTARY)
{
BYTE *last = base + MIN(buflen, MPEG_PACKET_LENGTH);
for(BYTE *b = buf; b < last; b++)
{
if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0xb8) // GOP
{
// xhhhhhmm mmmmxsss sssfffff fxxxxxxx
int hours = (b[4] >> 2) & 63;
int mins = ((b[4] & 3) << 4) | (b[5] >> 4);
int secs = ((b[5] & 7) << 3) | (b[6] >> 5);
int frames = ((b[6] << 1) & 63) | (b[7] >> 7);
pts = ((LONGLONG)(hours * 3600) + (LONGLONG)(mins * 60) + (LONGLONG)secs) *
INT64(90000) + (LONGLONG)frames * INT64(90000) / fps;
break;
}
}
// we don't need packets if we scan at the end of file
if (pts == 0 && !seek_start)
return 1;
}
#endif
if (seek_start)
{
if (pts > 0)
{
scan_for_length = false;
start_time_pts = pts;
UpdateTotalTime();
}
}
else
{
if (pts > 0 && pts > end_time_pts)
{
end_time_pts = pts;
scan_for_length = false;
return 1;
}
if (elementary_stream)
return 1;
continue;
}
}
// audio or video
MpegPacket *packet = NULL;
packet = mpeg_feed_getlast();
if (packet == NULL) // well, it won't really help
return 0;
memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4);
packet->pts = pts_base;
//!!!!!!!!!!!!!!!!!!!
packet->scr = scr;
bool feed = true;
if (packet_header_incomplete) // we haven't read the header last time
{
packet_left = 0;
packet_header_incomplete = false;
}
if (packet_left > 0)
{
packet->type = saved_packet->type;
packet->pData = buf;
packet->size = packet_left;
packet->pts = saved_packet->pts;
packet->nframeheaders = saved_packet->nframeheaders;
packet->firstaccessunitpointer = saved_packet->firstaccessunitpointer;
packet->scr = 0;
feed = saved_feed;
packet_left = 0;
} else
{
BYTE *lb = buf;
int ret = mpeg_extractpacket(buf, base, packet, sct, wait_for_new_pts);
if (ret < 0) // if not enough data in the packet
{
LONGLONG off = -(base + MPEG_PACKET_LENGTH - lb);
video_lseek(off, SEEK_CUR);
saved_sct = sct;
packet_left = 1; // the value doesn't really matter
packet_header_incomplete = true;
return 1;
}
if (ret == 0)
feed = false;
}
int left = MPEG_PACKET_LENGTH - (buf - base);
bool need_return = false;
if ((int)packet->size > left) // length is greater than packet size
{
packet_left = packet->size - left;
packet->size = left;
saved_sct = sct;
saved_packet = packet;
saved_feed = feed;
need_return = true;
}
if (new_frame_size)
{
int w, h;
mpeg_getframesize(&w, &h);
if (w != 0 && h != 0)
{
script_framesize_callback(w, h);
new_frame_size = false;
}
int aspect = mpeg_getaspect();
if (mpeg_aspect != aspect)
{
mpeg_aspect = aspect;
int tvtype = settings_get(SETTING_TVTYPE);
KHWL_VIDEOMODE vmode = KHWL_VIDEOMODE_NORMAL;
if (aspect == 2) // 4:3
{
if (tvtype == 0 || tvtype == 1)
vmode = KHWL_VIDEOMODE_NORMAL;
else
vmode = (tvtype == 2) ? KHWL_VIDEOMODE_HCENTER : KHWL_VIDEOMODE_VCENTER;
}
else if (aspect == 3) // 16:9
{
if (tvtype == 2 || tvtype == 3)
vmode = KHWL_VIDEOMODE_WIDE;
else
vmode = (tvtype == 0) ? KHWL_VIDEOMODE_LETTERBOX : KHWL_VIDEOMODE_PANSCAN;
}
msg("MPEG: set vmode = %d (%d)\n", vmode, aspect);
khwl_display_clear();
khwl_set_window_zoom(KHWL_ZOOMMODE_DVD);
khwl_setvideomode(vmode, TRUE);
}
int cur_fps = mpeg_get_fps();
if (cur_fps > 0)
{
fps = cur_fps;
script_framerate_callback(fps);
}
int cur_fmt = mpeg_get_video_format();
if (cur_fmt != mpeg_format)
{
if (!elementary_stream)
{
video_fmt_str.Empty();
video_fmt_str.Printf("MPEG-%d", cur_fmt == MPEG_1 ? 1 : 2);
script_video_info_callback(video_fmt_str);
MSG("Video: * Video format %s detected!\n", *video_fmt_str);
mpeg_format = cur_fmt;
}
}
}
if (packet->flags == 2 && feed)
{
if (wait_for_new_pts)
{
if (packet->type == 0)
{
if (mpeg_needs_seq_start_header(&packet))
wait_for_new_pts = false;
else
feed = false;
}
else
feed = false;
}
if (packet->type == 0)
{
pts_base += mpeg_detect_and_fix_pts_wrap(packet);
if (set_pts)
{
mpeg_setpts(packet->pts);
set_pts = false;
}
video_pts = packet->pts;
} else
{
int cur_numaudio_streams = mpeg_getaudiostreamsnum();
if (cur_numaudio_streams != num_tracks)
{
num_tracks = cur_numaudio_streams;
}
}
if (displ_pts_base == 0)
{
if (packet->type == 0)
{
displ_pts_base = -MAX(packet->pts - 45000, 0);
/*
pts_base = -MAX(packet->pts - 45000, 0);
packet->pts += pts_base;
scr = packet->scr = ((packet->scr & (~SPTM_SCR_FLAG)) + pts_base) | SPTM_SCR_FLAG;
*/
}
}
int rate = mpeg_getrate(elementary_stream);
if (rate != cur_rate)
{
// calc. total time
if (rate > 0)
{
UpdateTotalTime();
cur_rate = rate;
}
}
old_pts = packet->pts;
} else
{
if (wait_for_new_pts)
feed = false;
packet->pts = old_pts;
}
#if 0
{
static int num_packets = 0;
static LONGLONG last_pts = 0;
if (feed)
{
if (packet->type == 1)
msg("[%d] a - %d ---%d: %8d\t\t%d\t\t[%d]\t%d\n", num_packets, packet->size, packet->flags, (int)packet->pts, (int)(packet->scr & 0xfffffff), (int)(packet->pts - last_pts), pts_base);
else if (packet->type == 0)
msg("[%d] v - %d ---%d: %8d\t\t%d\t\t[%d]\t%d\n", num_packets, packet->size, packet->flags, (int)packet->pts, (int)(packet->scr & 0xfffffff), (int)(packet->pts - last_pts), pts_base);
last_pts = packet->pts;
}
/*
extern void player_printpacket(MpegPacket *packet);
player_printpacket(packet);
*/
num_packets++;
}
//!!!!!!!!!!!!!!!!!!!!!
/*
if (msg_get_output() == MSG_OUTPUT_SHOW && packet->type != 0)
feed = false;
if (msg_get_output() == MSG_OUTPUT_FREEZE && packet->type != 0 && packet->type != 1)
feed = false;
*/
//!!!!!!!!!!!!!!!!!!!!!
#endif
//packet->vobu_sptm = (vobu_sptm + pts_base) | SPTM_SCR_FLAG;
// increase bufidx
if (packet->size >= 1 && feed)
{
mpeg_setbufidx(MPEG_BUFFER_1, packet);
mpeg_feed((MPEG_FEED_TYPE)packet->type);
//num_packets++;
}
buf += packet->size;
lastbuf = buf;
if (need_return)
return 1;
}
}
return 1;
}
void VideoMpg::UpdateTotalTime()
{
LONGLONG new_totaltime = 0;
if (start_time_pts >= 0 && end_time_pts >= 0 && end_time_pts > start_time_pts)
{
new_totaltime = (end_time_pts - start_time_pts);
seek_filesize = filesize;
is_totaltime_from_rate = false;
//msg("! new_tt = %d (s=%d e=%d)\n", (int)new_totaltime, (int)start_time_pts, (int)end_time_pts);
}
else
{
LONGLONG rate = (LONGLONG)mpeg_getrate(elementary_stream);
if (rate > 0)
{
new_totaltime = INT64(90000) * filesize / rate;
seek_filesize = filesize;
is_totaltime_from_rate = true;
//msg("! new_tt = %d (r=%d fs=%d)\n", (int)new_totaltime, (int)rate, (int)filesize);
}
}
// if we're unable to deremine total time
if (video_pts + displ_pts_base > new_totaltime)
{
new_totaltime = video_pts + displ_pts_base;
seek_filesize = media_get_filepos();
is_totaltime_from_rate = false;
//msg("! video_pts (%d) > new_totaltime (%d)\n", (int)video_pts, (int)new_totaltime);
//msg("! seek_filesize = %d\n", (int)seek_filesize);
}
if (new_totaltime > 0)
{
if (new_totaltime > totaltime_pts || (is_totaltime_from_rate && Abs(new_totaltime - totaltime_pts) > 90000))
{
script_totaltime_callback((int)(new_totaltime / 90000));
totaltime_pts = new_totaltime;
}
}
}
int VideoMpg::GetNextIndexes()
{
return -1;
}
int VideoMpg::GetNextKeyFrame()
{
return -1;
}
int VideoMpg::GetKeyFrame(LONGLONG time)
{
// undo some Video actions for MPEGs:
skip_fwd = false;
skip_rev = false;
skip_cnt = 0;
searching = false;
packet_left = 0;
packet_header_incomplete = false;
old_pts = 0;
wait_for_new_pts = true;
set_pts = true;
was_pack = false;
saved_sct = START_CODE_UNKNOWN;
saved_length = -1;
saved_start_counter = 0;
saved_feed = true;
mpeg_stop();
mpeg_setspeed(MPEG_SPEED_NORMAL);
media_skip_buffer(NULL);
mpeg_setpts(0);
mpeg_start();
if (totaltime_pts < 1 || filesize < 1)
{
msg("MPEG: Cannot seek! Total time or size unknown.\n");
return -1;
}
LONGLONG curtime = saved_pts;
LONGLONG vpts = video_pts + displ_pts_base;
if (curtime < 0)
curtime = 0;
if (curtime > vpts)
curtime = vpts;
if (curtime < vpts - 10*90000)
curtime = vpts;
LONGLONG pts = 0;
if (time == VIDEO_KEY_FRAME_NEXT)
{
pts = curtime + seek_delta_pts;
}
else if (time == VIDEO_KEY_FRAME_PREV)
{
pts = curtime - seek_delta_pts;
}
else
{
pts = time * 90000;
}
LONGLONG filepos = (seek_filesize * pts / totaltime_pts) & (~(/*MPEG_PACKET_LENGTH*/512 - 1));
if (filepos < 0)
filepos = 0;
// a special correction fix for VBR MPEGs
if (time == VIDEO_KEY_FRAME_NEXT || time == VIDEO_KEY_FRAME_PREV)
{
LONGLONG rate = (LONGLONG)mpeg_getrate(elementary_stream);
if (rate > 0)
{
LONGLONG avg_rate = INT64(90000) * seek_filesize / totaltime_pts;
LONGLONG thr = 100 * Abs(rate - avg_rate) / avg_rate;
if (thr < 10)
{
LONGLONG delta_pos = filepos - media_get_filepos();
LONGLONG delta_time = INT64(90000) * delta_pos / rate;
LONGLONG d = Abs(delta_time) - seek_delta_pts;
d = seek_delta_pts * rate / INT64(90000);
if (time == VIDEO_KEY_FRAME_PREV)
d = -d;
filepos = media_get_filepos() + d;
msg("MPEG: * Bitrate-correcting (" PRINTF_64d ", r=" PRINTF_64d ")!\n", d, rate);
}
}
}
msg("(saved=%d fsize=%d)\n", (int)saved_pts, (int)seek_filesize);
msg("MPEG: Seek to " PRINTF_64d " (filepos=" PRINTF_64d "/" PRINTF_64d ")\n", pts, filepos, filesize);
LONGLONG ret = video_lseek(filepos, SEEK_SET);
if (ret != filepos)
msg("MPEG: Seek ERROR! Pos = " PRINTF_64d "\n", ret);
if (ret < 0)
{
msg("MPEG: Cannot seek! File error %d.\n", -errno);
return -1;
}
return 1;
}
#endif