/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Initial Developer of the Original Code is * CSIRO * Portions created by the Initial Developer are Copyright (C) 2007 * the Initial Developer. All Rights Reserved. * * Contributor(s): Shane Stephens, Michael Martin * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "std_semaphore.h" #if defined(XP_UX) #include #include #include #include #elif defined(XP_WIN) #include #endif #include #include #include "plugin_oggplay.h" #include "plugin_tools.h" #define LIBOGGPLAY_BUFFER_SIZE 20 #define AUDIO_TRACK_OFFSET 250L #include #include typedef struct { OggPlay * player; int shutdown_oggplay; char * location; char * proxy; int proxy_port; semaphore seek_sem; volatile int seek_flag; long seek_pos; OggPlayErrorCode seek_err; semaphore start_stop_sem; long last_displayed_frame_time; #if defined(XP_UX) pthread_t thread; #elif defined(XP_WIN) HANDLE thread; #elif defined(XP_MACOSX) pthread_t thread; #endif int audio_rate; int video_rate; int audio_channels; int callback_period; int reference_track; int duration; int available; int finished; nsPluginInstance *pluginInstance; } PluginPointers; int get_audio_rate(void *ptrs) { return ((PluginPointers *)ptrs)->audio_rate; } int get_audio_channels(void *ptrs) { return ((PluginPointers *)ptrs)->audio_channels; } int get_video_rate(void *ptrs) { return ((PluginPointers *)ptrs)->video_rate; } int get_callback_period(void *ptrs) { return ((PluginPointers *)ptrs)->callback_period; } /** helper functions for audio data aggregation */ void buffer_audio_data(unsigned char ** buffer, int* buffer_length, unsigned char * data, int samples, int sample_size, int channels); void float_to_short_array(const float* in, short* out, int len); #define NUM_DENOM_TO_16_16(n, d, o) \ do {ogg_int64_t l = (n); l <<= 16; l /= (d); *(o) = l;} while (0) #if defined(XP_UX) || defined(XP_MACOSX) static void * decoding_thread(void *handle) { #elif defined(XP_WIN) static DWORD WINAPI decoding_thread(void *handle) { #endif PluginPointers * pointers = (PluginPointers *)handle; OggPlayReader * reader; OggPlay * player; int i; int video_track = -1; int audio_track = -1; ogg_int64_t time_ref; reader = oggplay_tcp_reader_new(pointers->location, pointers->proxy, pointers->proxy_port); player = oggplay_new_with_reader(reader); pointers->shutdown_oggplay = 0; pointers->player = player; if (player == NULL) { #if defined(XP_UX) || defined(XP_MACOSX) return NULL; #elif defined(XP_WIN) return 0; #endif } oggplay_use_buffer(player, LIBOGGPLAY_BUFFER_SIZE); SEM_SIGNAL(pointers->start_stop_sem); /* * Wrap the oggplay startup process in a ~30 sec timeout. If we tell * oggplay_initialise() not to block, it will use non-blocking I/O * calls and return if any operation takes longer than some small * period of time to complete. */ time_ref = oggplay_sys_time_in_ms(); while (1) { OggPlayErrorCode res = oggplay_initialise(player, 0); if (res == E_OGGPLAY_OK) { break; } if (pointers->shutdown_oggplay) { goto thread_shutdown; } if (res == E_OGGPLAY_TIMEOUT) { if (oggplay_sys_time_in_ms() - time_ref < 30000) { continue; } } while (!pointers->shutdown_oggplay) { oggplay_millisleep(10); } goto thread_shutdown; } for (i = 0; i < oggplay_get_num_tracks(player); i++) { if (oggplay_get_track_type(player, i) == OGGZ_CONTENT_THEORA) { int denom, num; oggplay_set_callback_num_frames(player, i, 1); oggplay_set_track_active(player, i); oggplay_get_video_fps(player, i, &denom, &num); NUM_DENOM_TO_16_16(num, denom, &(pointers->video_rate)); NUM_DENOM_TO_16_16(denom * 1000, num, &(pointers->callback_period)); video_track = i; #ifdef USE_AUDIO } else if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_VORBIS) { oggplay_set_track_active(player, i); oggplay_set_offset(player, i, AUDIO_TRACK_OFFSET); oggplay_get_audio_samplerate(player, i, &(pointers->audio_rate)); oggplay_get_audio_channels(player, i, &(pointers->audio_channels)); audio_track = i; } else if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_SPEEX) { oggplay_set_track_active(player, i); oggplay_set_offset(player, i, AUDIO_TRACK_OFFSET); oggplay_get_audio_samplerate(player, i, &(pointers->audio_rate)); oggplay_get_audio_channels(player, i, &(pointers->audio_channels)); audio_track = i; #endif } else if (oggplay_get_track_type(player, i) == OGGZ_CONTENT_CMML) { oggplay_set_track_active(player, i); } } #ifdef USE_AUDIO if (video_track == -1) { int period = pointers->audio_rate / 25; oggplay_set_callback_num_frames(player, audio_track, period); NUM_DENOM_TO_16_16(period * 1000, pointers->audio_rate, &(pointers->callback_period)); pointers->video_rate = 0; } #endif pointers->duration = oggplay_get_duration(pointers->player); /* * There are certain bits of info that the caller thinks of as applying * to the ogg media stream as a whole, but are actually different for each * track (for example, the audio track can reach end-of-stream before the * video track). So we need to choose one of the tracks as a reference, and * treat its status as representative of the whole media stream. */ pointers->reference_track = (video_track != -1) ? video_track : audio_track; printf("video rate: %f audio rate: %d audio channels: %d callback pd: %f\n", pointers->video_rate / 65536.0, pointers->audio_rate, pointers->audio_channels, pointers->callback_period/65536.0); pointers->finished = 0; while (1) { OggPlayErrorCode r; r = E_OGGPLAY_TIMEOUT; while (r == E_OGGPLAY_TIMEOUT) { if (pointers->shutdown_oggplay) { goto thread_shutdown; } r = oggplay_step_decoding(pointers->player); pointers->available = oggplay_get_available(pointers->player); if ( pointers->finished == 0 && oggplay_media_finished_retrieving(pointers->player) ) { pointers->finished = 1; onMovieDownload(pointers->pluginInstance); } if (pointers->seek_flag) { SEM_WAIT(pointers->seek_sem); pointers->seek_err = oggplay_seek(pointers->player, pointers->seek_pos); pointers->seek_flag = 0; SEM_SIGNAL(pointers->seek_sem); } } if (r != E_OGGPLAY_CONTINUE && r != E_OGGPLAY_USER_INTERRUPT) { break; } } /* * we've reached the end of the file, so let's wait for the gui thread * to tell us to finish up */ while (pointers->shutdown_oggplay == 0) { oggplay_millisleep(10); } thread_shutdown: pointers->shutdown_oggplay = 0; oggplay_close(pointers->player); SEM_SIGNAL(pointers->start_stop_sem); #if defined(XP_UX) || defined(XP_MACOSX) pthread_exit(NULL); #elif defined(XP_WIN) ExitThread(0); return 0; #endif } void * initialise_oggplay(nsPluginInstance *instance, char *location, char *proxy, int proxy_port) { #if defined(XP_WIN) int dec_id; #endif PluginPointers * pointers = (PluginPointers*)malloc(sizeof(PluginPointers)); pointers->pluginInstance = instance; pointers->player = NULL; pointers->location = strdup(location); pointers->proxy = (proxy != NULL) ? strdup(proxy) : NULL; pointers->proxy_port = proxy_port; pointers->last_displayed_frame_time = 0; pointers->video_rate = 0; pointers->audio_rate = 0; pointers->audio_channels = 0; pointers->callback_period = 0; pointers->reference_track = -1; pointers->duration = -1; pointers->available = -1; pointers->seek_flag = 0; pointers->seek_pos = 0; pointers->seek_err = E_OGGPLAY_OK; SEM_CREATE(pointers->seek_sem, 1); SEM_CREATE(pointers->start_stop_sem, 1); SEM_WAIT(pointers->start_stop_sem); #if defined(XP_UX) || defined(XP_MACOSX) pthread_create(&(pointers->thread), NULL, decoding_thread, (void *)pointers); #elif defined(XP_WIN) pointers->thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)decoding_thread, (LPVOID)pointers, 0, &dec_id); #endif SEM_WAIT(pointers->start_stop_sem); return pointers; } void shut_oggplay(void *handle) { PluginPointers * pointers = (PluginPointers *)handle; pointers->shutdown_oggplay = 1; /* * tell oggplay it's about to be shutdown */ oggplay_prepare_for_close(pointers->player); /* * wait for the oggplay object to shut itself down. */ SEM_WAIT(pointers->start_stop_sem); SEM_CLOSE(pointers->start_stop_sem); SEM_CLOSE(pointers->seek_sem); free(pointers->location); free(pointers); } void get_oggplay_frame(void *handle, PluginOggFrame *frame_data) { int i; int j; OggPlayCallbackInfo ** track_info; int required; #ifdef USE_AUDIO OggPlayAudioData * audio_data; #endif PluginPointers * pointers = (PluginPointers *)handle; OggPlay * player = pointers->player; int num_tracks; frame_data->video_data = NULL; frame_data->frame = NULL; frame_data->samples = NULL; frame_data->cmml_strings = NULL; frame_data->oggplay_info = NULL; /* * player not yet ready */ if (player == NULL) { return; } track_info = oggplay_buffer_retrieve_next(player); if (track_info == NULL) { return; } frame_data->oggplay_info = (void *)track_info; num_tracks = oggplay_get_num_tracks(player); for (i = 0; i < num_tracks; i++) { OggPlayDataType type; OggPlayDataHeader ** headers; type = oggplay_callback_info_get_type(track_info[i]); headers = oggplay_callback_info_get_headers(track_info[i]); switch (type) { case OGGPLAY_YUV_VIDEO: frame_data->video_data = oggplay_callback_info_get_video_data(headers[0]); oggplay_get_video_y_size(player, i, &frame_data->width, &frame_data->height); pointers->last_displayed_frame_time = oggplay_callback_info_get_presentation_time(headers[0]); break; #ifdef USE_AUDIO case OGGPLAY_FLOATS_AUDIO: required = oggplay_callback_info_get_required(track_info[i]); for (j = 0; j < required; j++) { int samples; samples = oggplay_callback_info_get_record_size(headers[j]); audio_data = oggplay_callback_info_get_audio_data(headers[j]); buffer_audio_data(&(frame_data->samples), &(frame_data->size), (unsigned char*)audio_data, samples, sizeof(short), pointers->audio_channels); }; /* no video data - get the presentation time of the audio instead */ if (pointers->video_rate == 0) { pointers->last_displayed_frame_time = oggplay_callback_info_get_presentation_time(headers[required - 1]); pointers->last_displayed_frame_time -= AUDIO_TRACK_OFFSET; } break; #endif case OGGPLAY_CMML: required = oggplay_callback_info_get_required(track_info[i]); for (j = 0; j < required; j++) { if (frame_data->cmml_strings == NULL) { frame_data->cmml_strings = malloc(required * sizeof(unsigned char*)); frame_data->cmml_size = required; } frame_data->cmml_strings[j] = oggplay_callback_info_get_text_data(headers[j]); } break; default: break; } } return; } void convert_oggplay_frame(void *handle, PluginOggFrame *frame_data, PluginColourFormat output_format) { OggPlayVideoData * video_data = frame_data->video_data; OggPlayYUVChannels yuv; OggPlayRGBChannels rgb; if (video_data == NULL || frame_data->frame != NULL) { return; } frame_data->frame = (unsigned char *)malloc(frame_data->width * frame_data->height * 4); yuv.ptry = video_data->y; yuv.ptru = video_data->u; yuv.ptrv = video_data->v; yuv.uv_height = frame_data->height / 2; yuv.uv_width = frame_data->width / 2; yuv.y_width = frame_data->width; yuv.y_height = frame_data->height; rgb.ptro = frame_data->frame; rgb.rgb_width = frame_data->width; rgb.rgb_height = frame_data->height; switch (output_format) { case BGR: oggplay_yuv2bgr(&yuv, &rgb); break; case RGB: default: oggplay_yuv2rgb(&yuv, &rgb); break; } } void free_oggplay_frame(void *handle, PluginOggFrame *frame_data) { PluginPointers * pointers = (PluginPointers *)handle; OggPlay * player = pointers->player; // free video frame memory if (frame_data->frame != NULL) { free(frame_data->frame); frame_data->frame = NULL; } // free audio data memory if (frame_data->samples != NULL) { free(frame_data->samples); frame_data->samples = NULL; } // free cmml data if (frame_data->cmml_strings != NULL) { free(frame_data->cmml_strings); frame_data->cmml_strings = NULL; } frame_data->size = 0; oggplay_buffer_release(player, (OggPlayCallbackInfo **)(frame_data->oggplay_info)); } OggPlayStreamInfo get_oggplay_stream_info(void *handle, PluginOggFrame *frame_data) { PluginPointers * pointers = (PluginPointers *)handle; OggPlayCallbackInfo ** track_info = frame_data->oggplay_info; int ref_track = pointers->reference_track; if (track_info == NULL || ref_track == -1) { return E_OGGPLAY_BAD_CALLBACK_INFO; } return oggplay_callback_info_get_stream_info(track_info[ref_track]); } /** * Scripting, dude, ... scripting */ long get_oggplay_play_position(void *handle) { PluginPointers *pointers = (PluginPointers *)handle; return pointers->last_displayed_frame_time; } int set_oggplay_play_position(void *handle, long milliseconds) { PluginPointers *pointers = (PluginPointers *)handle; SEM_WAIT(pointers->seek_sem); pointers->seek_pos = milliseconds; pointers->seek_flag = 1; SEM_SIGNAL(pointers->seek_sem); return TRUE; } int get_oggplay_available(void *handle) { return ((PluginPointers *)handle)->available; } int get_oggplay_duration(void *handle) { return ((PluginPointers *)handle)->duration; }