singe/vldp2/vldp/vldp.c
2019-11-11 14:53:02 -06:00

399 lines
11 KiB
C

/*
* vldp.c
*
* Copyright (C) 2001 Matt Ownby
*
* This file is part of VLDP, a virtual laserdisc player.
*
* VLDP 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.
*
* VLDP 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
*/
// see vldp.h for documentation on how to use the API
#include <stdio.h>
#include <string.h>
#include "vldp.h"
#include "vldp_common.h"
#define API_VERSION 11
//////////////////////////////////////////////////////////////////////////////////////
int vldp_cmd(int cmd);
int vldp_wait_for_status(int stat);
//////////////////////////////////////////////////////////////////////////////////////
SDL_Thread *private_thread = NULL;
int p_initialized = 0; // whether VLDP has been initialized
Uint8 g_req_cmdORcount = CMDORCOUNT_INITIAL; // the current command parent thread requests of the child thread
unsigned int g_ack_count = ACK_COUNT_INITIAL; // the result returned by the internal child thread
char g_req_file[STRSIZE]; // requested mpeg filename
Uint32 g_req_timer = 0; // requests timer value to be used for mpeg playback
Uint32 g_req_frame = 0; // requested frame to search to
Uint32 g_req_min_seek_ms = 0; // seek must take at least this many milliseconds (simulate laserdisc seek delay)
unsigned int g_req_precache = VLDP_FALSE; // whether g_req_idx has any meaning
unsigned int g_req_idx = 0; // multipurpose index (used by precaching)
unsigned int g_req_skip_per_frame = 0; // how many frames to skip per frame (for playing at 2X for example)
unsigned int g_req_stall_per_frame = 0; // how many frames to stall per frame (for playing at 1/2X for example)
struct vldp_out_info g_out_info; // contains info that the parent thread should have access to
const struct vldp_in_info *g_in_info; // contains info from parent thread that VLDP should have access to
/////////////////////////////////////////////////////////////////////
// issues a command to the internal thread and returns 1 if the internal thread acknowledged our command
// or 0 if we timed out without getting a response
// NOTE : this does not mean that the internal thread has finished executing our requested command, only
// that the command has been received
int vldp_cmd(int cmd)
{
int result = 0;
Uint32 cur_time = g_in_info->GetTicksFunc();
Uint8 tmp = g_req_cmdORcount; // we want to replace the real value atomically so we use a tmp variable first
static unsigned int old_ack_count = ACK_COUNT_INITIAL;
tmp++; // increment the counter so child thread knows we're issuing a new command
tmp &= 0xF; // strip off old command
tmp |= cmd; // replace it with new command
g_req_cmdORcount = tmp; // here is the atomic replacement
// loop until we timeout or get a response
while ((g_in_info->GetTicksFunc() - cur_time) < VLDP_TIMEOUT)
{
// if the count has changed, it means the other thread has acknowledged our new command
if (g_ack_count != old_ack_count)
{
result = 1;
old_ack_count = g_ack_count; // prepare to receive the next command
break;
}
SDL_Delay(0); // switch to other thread
}
// if we weren't able to communicate, notify user
if (!result)
{
fprintf(stderr, "VLDP error! Timed out waiting for internal thread to accept command!\n");
}
return result;
}
// waits until the disc status is 'stat'
// if the stat we want is received, return 1 (success)
// if we time out, or if we get an error stat, return 0 (error)
// if we are legitimately busy, we return 2 (busy)
int vldp_wait_for_status(int stat)
{
int result = 0; // assume error unless we explicitly
int done = 0;
Uint32 cur_time = g_in_info->GetTicksFunc();
while (!done && ((g_in_info->GetTicksFunc() - cur_time) < VLDP_TIMEOUT))
{
if (g_out_info.status == stat)
{
done = 1;
result = 1;
}
else if (g_out_info.status == STAT_ERROR)
{
done = 1;
}
// else keep waiting
SDL_Delay(0); // switch to other thread, timing is critical so we delay for 0
}
// if we timed out but are busy, indicate that
if (g_out_info.status == STAT_BUSY)
{
result = 2;
}
// else if we timed out
else if ((g_in_info->GetTicksFunc() - cur_time) >= VLDP_TIMEOUT)
{
fprintf(stderr, "VLDP ERROR!!!! Timed out with getting our expected response!\n");
}
return result;
}
//////////////////////////////////////////////////////////
void vldp_shutdown()
{
// only shutdown if we have previous initialized
if (p_initialized)
{
vldp_cmd(VLDP_REQ_QUIT);
SDL_WaitThread(private_thread, NULL); // wait for private thread to terminate
}
p_initialized = 0;
}
// requests that we open an mpeg file
// IMPORTANT : This function returns immediately once the open command is received, but the
// file will not be open until the VLDP status is 'VLDP_STOPPED'. Parent thread take heed.
int vldp_open(const char *filename)
{
int result = 0; // assume we're busy so the loop below works the first time
FILE *F = NULL;
if (p_initialized)
{
F = fopen(filename, "rb");
// if file exists, we can open it
if (F)
{
fclose(F);
SAFE_STRCPY(g_req_file, filename, sizeof(g_req_file));
g_req_precache = VLDP_FALSE; // we're not precaching ...
result = vldp_cmd(VLDP_REQ_OPEN);
}
else
{
fprintf(stderr, "VLDP ERROR : can't open file %s\n", filename);
}
}
return result;
}
VLDP_BOOL vldp_open_precached(unsigned int uIdx, const char *filename)
{
VLDP_BOOL bResult = VLDP_FALSE;
if (p_initialized)
{
// even though we're using an index, we still need filename to compute .dat filename
SAFE_STRCPY(g_req_file, filename, sizeof(g_req_file));
g_req_idx = uIdx;
g_req_precache = VLDP_TRUE;
bResult = vldp_cmd(VLDP_REQ_OPEN);
}
return bResult;
}
int vldp_open_and_block(const char *filename)
{
int result = 0;
result = vldp_open(filename);
// if the open message was received ...
if (result)
{
result = 2; // 2 is the code that wait_for_status returns when it is busy ...
while (result == 2)
{
result = vldp_wait_for_status(STAT_STOPPED);
SDL_Delay(1); // timing isn't critical here, so we can delay for 1 instead of 0
}
}
return result;
}
VLDP_BOOL vldp_precache(const char *filename)
{
VLDP_BOOL bResult = VLDP_FALSE;
if (p_initialized)
{
SAFE_STRCPY(g_req_file, filename, sizeof(g_req_file));
bResult = vldp_cmd(VLDP_REQ_PRECACHE);
}
// else return false
return bResult;
}
// issues search command and returns immediately to parent thread.
// Search will not be complete until the VLDP status is STAT_PAUSED
int vldp_search(Uint32 frame, Uint32 min_seek_ms)
{
int result = 0;
if (p_initialized)
{
g_req_frame = frame;
g_req_min_seek_ms = min_seek_ms;
result = vldp_cmd(VLDP_REQ_SEARCH);
}
return result;
}
// issues search command blocks until search is complete
int vldp_search_and_block(Uint32 frame, Uint32 min_seek_ms)
{
int result = 0;
if (p_initialized)
{
g_req_frame = frame;
g_req_min_seek_ms = min_seek_ms;
vldp_cmd(VLDP_REQ_SEARCH);
result = vldp_wait_for_status(STAT_PAUSED);
}
return result;
}
int vldp_play(Uint32 timer)
{
int result = 0;
if (p_initialized)
{
g_req_timer = timer;
vldp_cmd(VLDP_REQ_PLAY);
result = vldp_wait_for_status(STAT_PLAYING); // play could get an error if we're at EOF
}
return(result);
}
int vldp_skip(Uint32 frame)
{
int result = 0;
// we can only skip if the mpeg is already playing (esp. since we don't accept a timer as an argument)
if (p_initialized && (g_out_info.status == STAT_PLAYING))
{
g_req_frame = frame;
g_req_min_seek_ms = 0; // just for safety purposes, we want to ensure that there is no minimum skip delay
result = vldp_cmd(VLDP_REQ_SKIP);
#ifdef VLDP_DEBUG
if (!result) fprintf(stderr, "vldp_cmd rejected SKIP request!\n");
#endif
}
#ifdef VLDP_DEBUG
// remove me once this bug is discovered
else
{
fprintf(stderr, "VLDP skip failed because one of these happened: \n");
fprintf(stderr, "p_initialized is %d\n", p_initialized);
fprintf(stderr, "g_out_info.status == %d\n", g_out_info.status);
}
#endif // VLDP_DEBUG
return result;
}
int vldp_pause()
{
int result = 0;
if (p_initialized)
{
result = vldp_cmd(VLDP_REQ_PAUSE);
}
return result;
}
int vldp_step_forward()
{
int result = 0;
if (p_initialized)
{
result = vldp_cmd(VLDP_REQ_STEP_FORWARD);
}
return result;
}
int vldp_stop()
{
return 0; // this is a useless function which I haven't finished yet anyway, so don't use it :)
}
VLDP_BOOL vldp_speedchange(unsigned int uSkipPerFrame, unsigned int uStallPerFrame)
{
VLDP_BOOL result = VLDP_FALSE;
if (p_initialized)
{
g_req_skip_per_frame = uSkipPerFrame;
g_req_stall_per_frame = uStallPerFrame;
result = vldp_cmd(VLDP_REQ_SPEEDCHANGE);
}
return result;
}
VLDP_BOOL vldp_lock(unsigned int uTimeoutMs)
{
VLDP_BOOL result = VLDP_FALSE;
if (p_initialized)
{
result = vldp_cmd(VLDP_REQ_LOCK);
}
return result;
}
VLDP_BOOL vldp_unlock(unsigned int uTimeoutMs)
{
VLDP_BOOL result = VLDP_FALSE;
if (p_initialized)
{
result = vldp_cmd(VLDP_REQ_UNLOCK);
}
return result;
}
///////////////////////////////////////////////////////////////////////////
// This comes at the end so I can avoid putting function declarations in vldp.h
// I want to keep these functions hidden and force the user to use the callbacks
const struct vldp_out_info *vldp_init(const struct vldp_in_info *in_info)
{
const struct vldp_out_info *result = NULL;
p_initialized = 0;
g_in_info = in_info;
// So parent thread knows if it's compatible with us
g_out_info.uApiVersion = API_VERSION;
// populate function pointers for parent thread's benefit
g_out_info.shutdown = vldp_shutdown;
g_out_info.open = vldp_open;
g_out_info.open_precached = vldp_open_precached;
g_out_info.open_and_block = vldp_open_and_block;
g_out_info.precache = vldp_precache;
g_out_info.play = vldp_play;
g_out_info.search = vldp_search;
g_out_info.search_and_block = vldp_search_and_block;
g_out_info.skip = vldp_skip;
g_out_info.pause = vldp_pause;
g_out_info.step_forward = vldp_step_forward;
g_out_info.stop = vldp_stop;
g_out_info.speedchange = vldp_speedchange;
g_out_info.lock = vldp_lock;
g_out_info.unlock = vldp_unlock;
private_thread = SDL_CreateThread(idle_handler, NULL); // start our internal thread
// if private thread was created successfully
if (private_thread)
{
p_initialized = 1;
result = &g_out_info;
}
return result;
}