ea568794e7
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@28862 a1c6a512-1295-4272-9138-f99709370657
1125 lines
26 KiB
C
1125 lines
26 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* AV stream manager implementation
|
|
*
|
|
* Copyright (c) 2007 Michael Sevakis
|
|
*
|
|
* 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include "plugin.h"
|
|
#include "mpegplayer.h"
|
|
#include "lib/grey.h"
|
|
#include "mpeg_settings.h"
|
|
|
|
#ifndef HAVE_LCD_COLOR
|
|
GREY_INFO_STRUCT_IRAM
|
|
#endif
|
|
|
|
static struct event_queue stream_mgr_queue SHAREDBSS_ATTR;
|
|
static struct queue_sender_list stream_mgr_queue_send SHAREDBSS_ATTR;
|
|
static uint32_t stream_mgr_thread_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)];
|
|
|
|
struct stream_mgr stream_mgr SHAREDBSS_ATTR;
|
|
|
|
/* Forward decs */
|
|
static int stream_on_close(void);
|
|
|
|
struct str_broadcast_data
|
|
{
|
|
long cmd; /* Command to send to stream */
|
|
intptr_t data; /* Data to send with command */
|
|
};
|
|
|
|
static inline void stream_mgr_lock(void)
|
|
{
|
|
rb->mutex_lock(&stream_mgr.str_mtx);
|
|
}
|
|
|
|
static inline void stream_mgr_unlock(void)
|
|
{
|
|
rb->mutex_unlock(&stream_mgr.str_mtx);
|
|
}
|
|
|
|
static inline void actl_lock(void)
|
|
{
|
|
rb->mutex_lock(&stream_mgr.actl_mtx);
|
|
}
|
|
|
|
static inline void actl_unlock(void)
|
|
{
|
|
rb->mutex_unlock(&stream_mgr.actl_mtx);
|
|
}
|
|
|
|
static inline void stream_mgr_post_msg(long id, intptr_t data)
|
|
{
|
|
rb->queue_post(stream_mgr.q, id, data);
|
|
}
|
|
|
|
static inline intptr_t stream_mgr_send_msg(long id, intptr_t data)
|
|
{
|
|
return rb->queue_send(stream_mgr.q, id, data);
|
|
}
|
|
|
|
static inline void stream_mgr_reply_msg(intptr_t retval)
|
|
{
|
|
rb->queue_reply(stream_mgr.q, retval);
|
|
}
|
|
|
|
int str_next_data_not_ready(struct stream *str)
|
|
{
|
|
/* Save the current window since it actually might be ready by the time
|
|
* the registration is received by buffering. */
|
|
off_t win_right = str->hdr.win_right;
|
|
|
|
if (str->hdr.win_right < disk_buf.filesize - MIN_BUFAHEAD &&
|
|
disk_buf.filesize > MIN_BUFAHEAD)
|
|
{
|
|
/* Set right edge to where probing left off + the minimum margin */
|
|
str->hdr.win_right += MIN_BUFAHEAD;
|
|
}
|
|
else
|
|
{
|
|
/* Request would be passed the end of the file */
|
|
str->hdr.win_right = disk_buf.filesize;
|
|
}
|
|
|
|
switch (disk_buf_send_msg(DISK_BUF_DATA_NOTIFY, (intptr_t)str))
|
|
{
|
|
case DISK_BUF_NOTIFY_OK:
|
|
/* Was ready - restore window and process */
|
|
str->hdr.win_right = win_right;
|
|
return STREAM_OK;
|
|
|
|
case DISK_BUF_NOTIFY_ERROR:
|
|
/* Error - quit parsing */
|
|
str_end_of_stream(str);
|
|
return STREAM_DATA_END;
|
|
|
|
default:
|
|
/* Not ready - go wait for notification from buffering. */
|
|
str->pkt_flags = 0;
|
|
return STREAM_DATA_NOT_READY;
|
|
}
|
|
}
|
|
|
|
void str_data_notify_received(struct stream *str)
|
|
{
|
|
/* Normalize win_right back to the packet length */
|
|
if (str->state == SSTATE_END)
|
|
return;
|
|
|
|
if (str->curr_packet == NULL)
|
|
{
|
|
/* Nothing was yet parsed since init */
|
|
str->hdr.win_right = str->hdr.win_left;
|
|
}
|
|
else
|
|
{
|
|
/* Restore window based upon current packet */
|
|
str->hdr.win_right = str->hdr.win_left +
|
|
(str->curr_packet_end - str->curr_packet);
|
|
}
|
|
}
|
|
|
|
/* Set stream manager to a "no-file" state */
|
|
static void stream_mgr_init_state(void)
|
|
{
|
|
stream_mgr.filename = NULL;
|
|
stream_mgr.resume_time = INVALID_TIMESTAMP;
|
|
stream_mgr.seeked = false;
|
|
}
|
|
|
|
/* Add a stream to the playback pool */
|
|
void stream_add_stream(struct stream *str)
|
|
{
|
|
actl_lock();
|
|
|
|
list_remove_item(stream_mgr.strl, str);
|
|
list_add_item(stream_mgr.strl, str);
|
|
|
|
actl_unlock();
|
|
}
|
|
|
|
/* Callback for various list-moving operations */
|
|
static bool strl_enum_callback(struct stream *str, intptr_t data)
|
|
{
|
|
actl_lock();
|
|
|
|
list_remove_item(stream_mgr.strl, str);
|
|
|
|
if (data == 1)
|
|
list_add_item(stream_mgr.actl, str);
|
|
|
|
actl_unlock();
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Clear all streams from active and playback pools */
|
|
void stream_remove_streams(void)
|
|
{
|
|
list_enum_items(stream_mgr.strl,
|
|
(list_enum_callback_t)strl_enum_callback, 0);
|
|
}
|
|
|
|
/* Move the playback pool to the active list */
|
|
void move_strl_to_actl(void)
|
|
{
|
|
list_enum_items(stream_mgr.strl,
|
|
(list_enum_callback_t)strl_enum_callback, 1);
|
|
}
|
|
|
|
/* Remove a stream from the active list and return it to the pool */
|
|
static bool actl_stream_remove(struct stream *str)
|
|
{
|
|
bool retval;
|
|
|
|
actl_lock();
|
|
|
|
retval = list_remove_item(stream_mgr.actl, str);
|
|
|
|
if (retval)
|
|
list_add_item(stream_mgr.strl, str);
|
|
|
|
actl_unlock();
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Broadcast a message to all active streams */
|
|
static bool actl_stream_broadcast_callback(struct stream *str,
|
|
struct str_broadcast_data *sbd)
|
|
{
|
|
switch (sbd->cmd)
|
|
{
|
|
case STREAM_PLAY:
|
|
case STREAM_PAUSE:
|
|
break;
|
|
|
|
case STREAM_STOP:
|
|
if (sbd->data != 0)
|
|
{
|
|
actl_lock();
|
|
|
|
list_remove_item(stream_mgr.actl, str);
|
|
list_add_item(stream_mgr.strl, str);
|
|
|
|
actl_unlock();
|
|
sbd->data = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
str_send_msg(str, sbd->cmd, sbd->data);
|
|
return true;
|
|
}
|
|
|
|
static void actl_stream_broadcast(int cmd, intptr_t data)
|
|
{
|
|
struct str_broadcast_data sbd;
|
|
sbd.cmd = cmd;
|
|
sbd.data = data;
|
|
list_enum_items(stream_mgr.actl,
|
|
(list_enum_callback_t)actl_stream_broadcast_callback,
|
|
(intptr_t)&sbd);
|
|
}
|
|
|
|
/* Set the current base clock */
|
|
static void set_stream_clock(uint32_t time)
|
|
{
|
|
/* Fudge: Start clock 100ms early to allow for some filling time */
|
|
if (time > 100*TS_SECOND/1000)
|
|
time -= 100*TS_SECOND/1000;
|
|
else
|
|
time = 0;
|
|
|
|
pcm_output_set_clock(TS_TO_TICKS(time));
|
|
}
|
|
|
|
static void stream_start_playback(uint32_t time, bool fill_buffer)
|
|
{
|
|
if (stream_mgr.seeked)
|
|
{
|
|
/* Clear any seeked status */
|
|
stream_mgr.seeked = false;
|
|
|
|
/* Flush old PCM data */
|
|
pcm_output_flush();
|
|
|
|
/* Set the master clock */
|
|
set_stream_clock(time);
|
|
|
|
/* Make sure streams are back in active pool */
|
|
move_strl_to_actl();
|
|
|
|
/* Prepare the parser and associated streams */
|
|
parser_prepare_streaming();
|
|
}
|
|
|
|
/* Start buffer which optional force fill */
|
|
disk_buf_send_msg(STREAM_PLAY, fill_buffer);
|
|
|
|
/* Tell each stream to start - may generate end of stream signals
|
|
* now - we'll handle this when finished */
|
|
actl_stream_broadcast(STREAM_PLAY, 0);
|
|
|
|
/* Actually start the clock */
|
|
pcm_output_play_pause(true);
|
|
}
|
|
|
|
/* Return the play time relative to the specified play time */
|
|
static uint32_t time_from_whence(uint32_t time, int whence)
|
|
{
|
|
int64_t currtime;
|
|
uint32_t start;
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
/* Set the current time (time = unsigned offset from 0) */
|
|
if (time > str_parser.duration)
|
|
time = str_parser.duration;
|
|
break;
|
|
case SEEK_CUR:
|
|
/* Seek forward or backward from the current time
|
|
* (time = signed offset from current) */
|
|
currtime = stream_get_seek_time(&start);
|
|
currtime -= start;
|
|
currtime += (int32_t)time;
|
|
|
|
if (currtime < 0)
|
|
currtime = 0;
|
|
else if ((uint64_t)currtime > str_parser.duration)
|
|
currtime = str_parser.duration;
|
|
|
|
time = (uint32_t)currtime;
|
|
break;
|
|
case SEEK_END:
|
|
/* Seek from the end (time = unsigned offset from end) */
|
|
if (time > str_parser.duration)
|
|
time = str_parser.duration;
|
|
time = str_parser.duration - time;
|
|
break;
|
|
}
|
|
|
|
return time;
|
|
}
|
|
|
|
/* Handle seeking details if playing or paused */
|
|
static uint32_t stream_seek_intl(uint32_t time, int whence,
|
|
int status, bool *was_buffering)
|
|
{
|
|
if (status != STREAM_STOPPED)
|
|
{
|
|
bool wb;
|
|
|
|
/* Place streams in a non-running state - keep them on actl if
|
|
* still there */
|
|
actl_stream_broadcast(STREAM_PAUSE, 0);
|
|
|
|
/* Stop all buffering or else risk clobbering random-access data */
|
|
wb = disk_buf_send_msg(STREAM_STOP, 0);
|
|
|
|
if (was_buffering != NULL)
|
|
*was_buffering = wb;
|
|
}
|
|
|
|
time = time_from_whence(time, whence);
|
|
|
|
stream_mgr.seeked = true;
|
|
|
|
return parser_seek_time(time);
|
|
}
|
|
|
|
/* Store the resume time at the last seek/current clock point */
|
|
static void stream_remember_resume_time(void)
|
|
{
|
|
/* Assume invalidity */
|
|
stream_mgr.resume_time = 0;
|
|
|
|
if (stream_can_seek())
|
|
{
|
|
/* Read the current stream time or the last seeked position */
|
|
uint32_t start;
|
|
uint32_t time = stream_get_seek_time(&start);
|
|
|
|
if (time >= str_parser.start_pts && time <= str_parser.end_pts)
|
|
{
|
|
/* Save the current stream time */
|
|
stream_mgr.resume_time = time - start;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Handle STREAM_OPEN */
|
|
void stream_on_open(const char *filename)
|
|
{
|
|
int err = STREAM_ERROR;
|
|
|
|
stream_mgr_lock();
|
|
|
|
trigger_cpu_boost();
|
|
|
|
/* Open the video file */
|
|
if (disk_buf_open(filename) >= 0)
|
|
{
|
|
/* Initialize the parser */
|
|
err = parser_init_stream();
|
|
|
|
if (err >= STREAM_OK)
|
|
{
|
|
/* File ok - save the opened filename */
|
|
stream_mgr.filename = filename;
|
|
}
|
|
}
|
|
|
|
/* If error - cleanup */
|
|
if (err < STREAM_OK)
|
|
stream_on_close();
|
|
|
|
cancel_cpu_boost();
|
|
|
|
stream_mgr_unlock();
|
|
|
|
stream_mgr_reply_msg(err);
|
|
}
|
|
|
|
/* Handler STREAM_PLAY */
|
|
static void stream_on_play(void)
|
|
{
|
|
int status = stream_mgr.status;
|
|
|
|
stream_mgr_lock();
|
|
|
|
if (status == STREAM_STOPPED)
|
|
{
|
|
uint32_t start;
|
|
|
|
/* We just say we're playing now */
|
|
stream_mgr.status = STREAM_PLAYING;
|
|
|
|
/* Reply with previous state */
|
|
stream_mgr_reply_msg(status);
|
|
|
|
trigger_cpu_boost();
|
|
|
|
/* Seek to initial position and set clock to that time */
|
|
|
|
/* Save the resume time */
|
|
stream_remember_resume_time();
|
|
|
|
/* Prepare seek to start point */
|
|
start = stream_seek_intl(stream_mgr.resume_time, SEEK_SET,
|
|
STREAM_STOPPED, NULL);
|
|
|
|
/* Sync and start - force buffer fill */
|
|
stream_start_playback(start, true);
|
|
}
|
|
else
|
|
{
|
|
/* Reply with previous state */
|
|
stream_mgr_reply_msg(status);
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
/* Handle STREAM_PAUSE */
|
|
static void stream_on_pause(void)
|
|
{
|
|
int status = stream_mgr.status;
|
|
|
|
stream_mgr_lock();
|
|
|
|
/* Reply with previous state */
|
|
stream_mgr_reply_msg(status);
|
|
|
|
if (status == STREAM_PLAYING)
|
|
{
|
|
/* Pause the clock */
|
|
pcm_output_play_pause(false);
|
|
|
|
/* Pause each active stream */
|
|
actl_stream_broadcast(STREAM_PAUSE, 0);
|
|
|
|
/* Pause the disk buffer - buffer may continue filling */
|
|
disk_buf_send_msg(STREAM_PAUSE, false);
|
|
|
|
/* Unboost the CPU */
|
|
cancel_cpu_boost();
|
|
|
|
/* Offically paused */
|
|
stream_mgr.status = STREAM_PAUSED;
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
/* Handle STREAM_RESUME */
|
|
static void stream_on_resume(void)
|
|
{
|
|
int status = stream_mgr.status;
|
|
|
|
stream_mgr_lock();
|
|
|
|
/* Reply with previous state */
|
|
stream_mgr_reply_msg(status);
|
|
|
|
if (status == STREAM_PAUSED)
|
|
{
|
|
/* Boost the CPU */
|
|
trigger_cpu_boost();
|
|
|
|
/* Sync and start - no force buffering */
|
|
stream_start_playback(str_parser.last_seek_time, false);
|
|
|
|
/* Officially playing */
|
|
stream_mgr.status = STREAM_PLAYING;
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
/* Handle STREAM_STOP */
|
|
static void stream_on_stop(bool reply)
|
|
{
|
|
int status = stream_mgr.status;
|
|
|
|
stream_mgr_lock();
|
|
|
|
if (reply)
|
|
stream_mgr_reply_msg(status);
|
|
|
|
if (status != STREAM_STOPPED)
|
|
{
|
|
/* Pause the clock */
|
|
pcm_output_play_pause(false);
|
|
|
|
/* Update the resume time info */
|
|
stream_remember_resume_time();
|
|
|
|
/* Not stopped = paused or playing */
|
|
stream_mgr.seeked = false;
|
|
|
|
/* Stop buffering */
|
|
disk_buf_send_msg(STREAM_STOP, 0);
|
|
|
|
/* Clear any still-active streams and remove from actl */
|
|
actl_stream_broadcast(STREAM_STOP, 1);
|
|
|
|
/* Stop PCM output (and clock) */
|
|
pcm_output_stop();
|
|
|
|
/* Cancel our processor boost */
|
|
cancel_cpu_boost();
|
|
|
|
stream_mgr.status = STREAM_STOPPED;
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
/* Handle STREAM_SEEK */
|
|
static void stream_on_seek(struct stream_seek_data *skd)
|
|
{
|
|
uint32_t time = skd->time;
|
|
int whence = skd->whence;
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
case SEEK_CUR:
|
|
case SEEK_END:
|
|
if (stream_mgr.filename == NULL)
|
|
break;
|
|
|
|
/* Keep things spinning if already doing so */
|
|
stream_keep_disk_active();
|
|
|
|
/* Have data - reply in order to acquire lock */
|
|
stream_mgr_reply_msg(STREAM_OK);
|
|
|
|
stream_mgr_lock();
|
|
|
|
/* Either seeking must be possible or a full rewind must be done */
|
|
if (stream_can_seek() || time_from_whence(time, whence) == 0)
|
|
{
|
|
bool buffer;
|
|
|
|
if (stream_mgr.status == STREAM_PLAYING)
|
|
{
|
|
/* Keep clock from advancing while seeking */
|
|
pcm_output_play_pause(false);
|
|
}
|
|
|
|
time = stream_seek_intl(time, whence, stream_mgr.status, &buffer);
|
|
stream_remember_resume_time();
|
|
|
|
if (stream_mgr.status == STREAM_PLAYING)
|
|
{
|
|
/* Sync and restart - no force buffering */
|
|
stream_start_playback(time, buffer);
|
|
}
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
return;
|
|
}
|
|
|
|
/* Invalid parameter or no file */
|
|
stream_mgr_reply_msg(STREAM_ERROR);
|
|
}
|
|
|
|
/* Handle STREAM_CLOSE */
|
|
static int stream_on_close(void)
|
|
{
|
|
int status = STREAM_STOPPED;
|
|
|
|
stream_mgr_lock();
|
|
|
|
/* Any open file that was accepted for playback? */
|
|
if (stream_mgr.filename != NULL)
|
|
{
|
|
/* Yes - hide video */
|
|
stream_show_vo(false);
|
|
/* Stop any playback */
|
|
status = stream_mgr.status;
|
|
stream_on_stop(false);
|
|
/* Tell parser file is finished */
|
|
parser_close_stream();
|
|
/* Reinitialize manager */
|
|
stream_mgr_init_state();
|
|
}
|
|
|
|
/* Let disk buffer reset itself - file might be open even if no good */
|
|
disk_buf_close();
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
/* Handle STREAM_EV_COMPLETE */
|
|
static void stream_on_ev_complete(struct stream *str)
|
|
{
|
|
stream_mgr_lock();
|
|
|
|
/* Stream is active? */
|
|
if (actl_stream_remove(str))
|
|
{
|
|
/* No - remove this stream from the active list */
|
|
DEBUGF(" finished: 0x%02x\n", str->id);
|
|
if (list_is_empty(stream_mgr.actl))
|
|
{
|
|
/* All streams have acked - stop playback */
|
|
stream_on_stop(false);
|
|
stream_mgr.resume_time = 0; /* Played to end - no resume */
|
|
}
|
|
else
|
|
{
|
|
/* Stream is done - stop it and place back in pool */
|
|
str_send_msg(str, STREAM_STOP, 1);
|
|
}
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
/* Callback for stream to notify about events internal to them */
|
|
void stream_generate_event(struct stream *str, long id, intptr_t data)
|
|
{
|
|
if (str == NULL)
|
|
return;
|
|
|
|
switch (id)
|
|
{
|
|
case STREAM_EV_COMPLETE:
|
|
/* The last stream has ended */
|
|
stream_mgr_post_msg(STREAM_EV_COMPLETE, (intptr_t)str);
|
|
break;
|
|
}
|
|
|
|
(void)data;
|
|
}
|
|
|
|
/* Clear any particular notification for which a stream registered */
|
|
void stream_clear_notify(struct stream *str, int for_msg)
|
|
{
|
|
switch (for_msg)
|
|
{
|
|
case DISK_BUF_DATA_NOTIFY:
|
|
disk_buf_send_msg(DISK_BUF_CLEAR_DATA_NOTIFY, (intptr_t)str);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Special handling for certain messages since they involve multiple
|
|
* operations behind the scenes */
|
|
static intptr_t send_video_msg(long id, intptr_t data)
|
|
{
|
|
intptr_t retval = 0;
|
|
|
|
if (video_str.thread != 0 && disk_buf.in_file >= 0)
|
|
{
|
|
|
|
switch (id)
|
|
{
|
|
case VIDEO_DISPLAY_SHOW:
|
|
if (data != 0 && disk_buf_status() == STREAM_STOPPED)
|
|
{ /* Only prepare image if showing and not playing */
|
|
parser_prepare_image(str_parser.last_seek_time);
|
|
}
|
|
break;
|
|
|
|
case VIDEO_PRINT_FRAME:
|
|
if (data)
|
|
break;
|
|
case VIDEO_PRINT_THUMBNAIL:
|
|
if (disk_buf_status() != STREAM_STOPPED)
|
|
break; /* Prepare image if not playing */
|
|
|
|
if (!parser_prepare_image(str_parser.last_seek_time))
|
|
return false; /* Preparation failed */
|
|
|
|
/* Image ready - pass message to video thread */
|
|
break;
|
|
}
|
|
|
|
retval = str_send_msg(&video_str, id, data);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Show/hide the video output */
|
|
bool stream_show_vo(bool show)
|
|
{
|
|
bool vis;
|
|
stream_mgr_lock();
|
|
|
|
vis = send_video_msg(VIDEO_DISPLAY_SHOW, show);
|
|
#ifndef HAVE_LCD_COLOR
|
|
grey_show(show);
|
|
#endif
|
|
stream_mgr_unlock();
|
|
|
|
return vis;
|
|
}
|
|
|
|
/* Query the visibility of video output */
|
|
bool stream_vo_is_visible(void)
|
|
{
|
|
bool vis;
|
|
stream_mgr_lock();
|
|
vis = send_video_msg(VIDEO_DISPLAY_IS_VISIBLE, 0);
|
|
stream_mgr_unlock();
|
|
return vis;
|
|
}
|
|
|
|
/* Return the video dimensions */
|
|
bool stream_vo_get_size(struct vo_ext *sz)
|
|
{
|
|
bool retval = false;
|
|
|
|
stream_mgr_lock();
|
|
|
|
if (str_parser.dims.w > 0 && str_parser.dims.h > 0)
|
|
{
|
|
*sz = str_parser.dims;
|
|
retval = true;
|
|
}
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return retval;
|
|
}
|
|
|
|
void stream_vo_set_clip(const struct vo_rect *rc)
|
|
{
|
|
stream_mgr_lock();
|
|
|
|
if (rc)
|
|
{
|
|
stream_mgr.parms.rc = *rc;
|
|
rc = &stream_mgr.parms.rc;
|
|
}
|
|
|
|
send_video_msg(VIDEO_SET_CLIP_RECT, (intptr_t)rc);
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
#ifndef HAVE_LCD_COLOR
|
|
/* Show/hide the gray video overlay (independently of vo visibility). */
|
|
void stream_gray_show(bool show)
|
|
{
|
|
stream_mgr_lock();
|
|
|
|
grey_show(show);
|
|
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
#endif /* !HAVE_LCD_COLOR */
|
|
|
|
/* Display a thumbnail at the last seek point */
|
|
bool stream_display_thumb(const struct vo_rect *rc)
|
|
{
|
|
bool retval;
|
|
|
|
if (rc == NULL)
|
|
return false;
|
|
|
|
stream_mgr_lock();
|
|
|
|
stream_mgr.parms.rc = *rc;
|
|
retval = send_video_msg(VIDEO_PRINT_THUMBNAIL,
|
|
(intptr_t)&stream_mgr.parms.rc);
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool stream_draw_frame(bool no_prepare)
|
|
{
|
|
bool retval;
|
|
stream_mgr_lock();
|
|
|
|
retval = send_video_msg(VIDEO_PRINT_FRAME, no_prepare);
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return retval;
|
|
}
|
|
|
|
/* Return the time playback should resume if interrupted */
|
|
uint32_t stream_get_resume_time(void)
|
|
{
|
|
uint32_t resume_time;
|
|
|
|
/* A stop request is async and replies before setting this - must lock */
|
|
stream_mgr_lock();
|
|
|
|
resume_time = stream_mgr.resume_time;
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return resume_time;
|
|
}
|
|
|
|
uint32_t stream_get_seek_time(uint32_t *start)
|
|
{
|
|
uint32_t time;
|
|
|
|
stream_mgr_lock();
|
|
|
|
if (stream_mgr.seeked)
|
|
{
|
|
time = str_parser.last_seek_time;
|
|
}
|
|
else
|
|
{
|
|
time = TICKS_TO_TS(pcm_output_get_clock());
|
|
|
|
/* Clock can be start early so keep in range */
|
|
if (time < str_parser.start_pts)
|
|
time = str_parser.start_pts;
|
|
}
|
|
|
|
if (start != NULL)
|
|
*start = str_parser.start_pts;
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return time;
|
|
}
|
|
|
|
/* Wait for a state transistion to complete */
|
|
void stream_wait_status(void)
|
|
{
|
|
stream_mgr_lock();
|
|
stream_mgr_unlock();
|
|
}
|
|
|
|
/* Returns the smallest file window that includes all active streams'
|
|
* windows */
|
|
static bool stream_get_window_callback(struct stream *str,
|
|
struct stream_window *sw)
|
|
{
|
|
off_t swl = str->hdr.win_left;
|
|
off_t swr = str->hdr.win_right;
|
|
|
|
if (swl < sw->left)
|
|
sw->left = swl;
|
|
|
|
if (swr > sw->right)
|
|
sw->right = swr;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool stream_get_window(struct stream_window *sw)
|
|
{
|
|
if (sw == NULL)
|
|
return false;
|
|
|
|
sw->left = LONG_MAX;
|
|
sw->right = LONG_MIN;
|
|
|
|
actl_lock();
|
|
list_enum_items(stream_mgr.actl,
|
|
(list_enum_callback_t)stream_get_window_callback,
|
|
(intptr_t)sw);
|
|
actl_unlock();
|
|
|
|
return sw->left <= sw->right;
|
|
}
|
|
|
|
/* Playback control thread */
|
|
static void stream_mgr_thread(void)
|
|
{
|
|
struct queue_event ev;
|
|
|
|
while (1)
|
|
{
|
|
rb->queue_wait(stream_mgr.q, &ev);
|
|
|
|
switch (ev.id)
|
|
{
|
|
case STREAM_OPEN:
|
|
stream_on_open((const char *)ev.data);
|
|
break;
|
|
|
|
case STREAM_CLOSE:
|
|
stream_on_close();
|
|
break;
|
|
|
|
case STREAM_PLAY:
|
|
stream_on_play();
|
|
break;
|
|
|
|
case STREAM_PAUSE:
|
|
if (ev.data)
|
|
stream_on_resume();
|
|
else
|
|
stream_on_pause();
|
|
break;
|
|
|
|
case STREAM_STOP:
|
|
stream_on_stop(true);
|
|
break;
|
|
|
|
case STREAM_SEEK:
|
|
stream_on_seek((struct stream_seek_data *)ev.data);
|
|
break;
|
|
|
|
case STREAM_EV_COMPLETE:
|
|
stream_on_ev_complete((struct stream *)ev.data);
|
|
break;
|
|
|
|
case STREAM_QUIT:
|
|
if (stream_mgr.status != STREAM_STOPPED)
|
|
stream_on_stop(false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Stream command interface APIs */
|
|
|
|
/* Opens a new file */
|
|
int stream_open(const char *filename)
|
|
{
|
|
if (stream_mgr.thread != 0)
|
|
return stream_mgr_send_msg(STREAM_OPEN, (intptr_t)filename);
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Plays the current file starting at time 'start' */
|
|
int stream_play(void)
|
|
{
|
|
if (stream_mgr.thread != 0)
|
|
return stream_mgr_send_msg(STREAM_PLAY, 0);
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Pauses playback if playing */
|
|
int stream_pause(void)
|
|
{
|
|
if (stream_mgr.thread != 0)
|
|
return stream_mgr_send_msg(STREAM_PAUSE, false);
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Resumes playback if paused */
|
|
int stream_resume(void)
|
|
{
|
|
if (stream_mgr.thread != 0)
|
|
return stream_mgr_send_msg(STREAM_PAUSE, true);
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Stops playback if not stopped */
|
|
int stream_stop(void)
|
|
{
|
|
if (stream_mgr.thread != 0)
|
|
return stream_mgr_send_msg(STREAM_STOP, 0);
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Seeks playback time to/by the specified time */
|
|
int stream_seek(uint32_t time, int whence)
|
|
{
|
|
int ret;
|
|
|
|
if (stream_mgr.thread == 0)
|
|
return STREAM_ERROR;
|
|
|
|
stream_mgr_lock();
|
|
|
|
stream_mgr.parms.skd.time = time;
|
|
stream_mgr.parms.skd.whence = whence;
|
|
|
|
ret = stream_mgr_send_msg(STREAM_SEEK, (intptr_t)&stream_mgr.parms.skd);
|
|
|
|
stream_mgr_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Closes the current file */
|
|
int stream_close(void)
|
|
{
|
|
if (stream_mgr.thread != 0)
|
|
return stream_mgr_send_msg(STREAM_CLOSE, 0);
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Initializes the playback engine */
|
|
int stream_init(void)
|
|
{
|
|
void *mem;
|
|
size_t memsize;
|
|
|
|
stream_mgr.status = STREAM_STOPPED;
|
|
stream_mgr_init_state();
|
|
|
|
/* Initialize our window to the outside world first */
|
|
rb->mutex_init(&stream_mgr.str_mtx);
|
|
rb->mutex_init(&stream_mgr.actl_mtx);
|
|
|
|
stream_mgr.q = &stream_mgr_queue;
|
|
rb->queue_init(stream_mgr.q, false);
|
|
|
|
/* sets audiosize and returns buffer pointer */
|
|
mem = rb->plugin_get_audio_buffer(&memsize);
|
|
|
|
/* Initialize non-allocator blocks first */
|
|
#ifndef HAVE_LCD_COLOR
|
|
long greysize;
|
|
|
|
/* Greylib init handles all necessary cache alignment */
|
|
if (!grey_init(mem, memsize, GREY_BUFFERED|GREY_ON_COP,
|
|
LCD_WIDTH, LCD_HEIGHT, &greysize))
|
|
{
|
|
rb->splash(HZ, "greylib init failed!");
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
mem += greysize;
|
|
memsize -= greysize;
|
|
|
|
grey_clear_display();
|
|
#endif /* !HAVE_LCD_COLOR */
|
|
|
|
stream_mgr.thread = rb->create_thread(stream_mgr_thread,
|
|
stream_mgr_thread_stack, sizeof(stream_mgr_thread_stack),
|
|
0, "mpgstream_mgr" IF_PRIO(, PRIORITY_SYSTEM) IF_COP(, CPU));
|
|
|
|
rb->queue_enable_queue_send(stream_mgr.q, &stream_mgr_queue_send,
|
|
stream_mgr.thread);
|
|
|
|
if (stream_mgr.thread == 0)
|
|
{
|
|
rb->splash(HZ, "Could not create stream manager thread!");
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Wait for thread to initialize */
|
|
stream_mgr_send_msg(STREAM_NULL, 0);
|
|
|
|
/* Initialise our malloc buffer */
|
|
if (!mpeg_alloc_init(mem, memsize))
|
|
{
|
|
rb->splash(HZ, "Out of memory in stream_init");
|
|
}
|
|
/* These inits use the allocator */
|
|
else if (!pcm_output_init())
|
|
{
|
|
rb->splash(HZ, "Could not initialize PCM!");
|
|
}
|
|
else if (!audio_thread_init())
|
|
{
|
|
rb->splash(HZ, "Cannot create audio thread!");
|
|
}
|
|
else if (!video_thread_init())
|
|
{
|
|
rb->splash(HZ, "Cannot create video thread!");
|
|
}
|
|
/* Disk buffer takes max allotment of what's left so it must be last */
|
|
else if (!disk_buf_init())
|
|
{
|
|
rb->splash(HZ, "Cannot create buffering thread!");
|
|
}
|
|
else if (!parser_init())
|
|
{
|
|
rb->splash(HZ, "Parser init failed!");
|
|
}
|
|
else
|
|
{
|
|
return STREAM_OK;
|
|
}
|
|
|
|
return STREAM_ERROR;
|
|
}
|
|
|
|
/* Cleans everything up */
|
|
void stream_exit(void)
|
|
{
|
|
stream_close();
|
|
|
|
/* Stop the threads and wait for them to terminate */
|
|
video_thread_exit();
|
|
audio_thread_exit();
|
|
disk_buf_exit();
|
|
pcm_output_exit();
|
|
|
|
if (stream_mgr.thread != 0)
|
|
{
|
|
stream_mgr_post_msg(STREAM_QUIT, 0);
|
|
rb->thread_wait(stream_mgr.thread);
|
|
stream_mgr.thread = 0;
|
|
}
|
|
|
|
#ifndef HAVE_LCD_COLOR
|
|
grey_release();
|
|
#endif
|
|
}
|