/* SMPEG - SDL MPEG Player Library Copyright (C) 1999 Loki Entertainment Software - Modified by Michel Darricau from eProcess for popcorn - This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * Copyright (c) 1995 The Regents of the University of California. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without written agreement is * hereby granted, provided that the above copyright notice and the following * two paragraphs appear in all copies of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ /* * Portions of this software Copyright (c) 1995 Brown University. * All rights reserved. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose, without fee, and without written agreement * is hereby granted, provided that the above copyright notice and the * following two paragraphs appear in all copies of this software. * * IN NO EVENT SHALL BROWN UNIVERSITY BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF BROWN * UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * BROWN UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" * BASIS, AND BROWN UNIVERSITY HAS NO OBLIGATION TO PROVIDE MAINTENANCE, * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ /* Changes to make the code reentrant: Got rid of setjmp, longjmp deglobalized: EOF_flag, FilmState, curVidStream, bitOffset, bitLength, bitBuffer, sys_layer, input, seekValue, window, X Windows globals (to xinfo), curBits, ditherType, matched_depth, totNumFrames, realTimeStart Additional changes: Ability to play >1 movie (w/out CONTROLS) Make sure we do a full frame for each movie DISABLE_DITHER #ifdef to avoid compiling dithering code Changes to deal with non-MPEG streams Now deals with NO_DITHER, PPM_DITHER and noDisplayFlag==1 CONTROLS version now can deal with >1 movie -lsh@cs.brown.edu (Loring Holden) */ #include #include #include "video.h" #include "proto.h" #include "dither.h" #include "util.h" #include "MPEGvideo.h" #include "MPEGfilter.h" /*--------------------------------------------------------------*/ /* Define buffer length. */ #define BUF_LENGTH 80000 /* TODO: Eliminate these globals so multiple movies can be played. */ /* Quiet flag (verbose). */ int quietFlag = 1; /* Framerate, -1: specified in stream (default) 0: as fast as possible N (N>0): N frames/sec */ #ifdef TIME_MPEG int framerate = 0; #else int framerate = -1; #endif /* Flag for high quality at the expense of speed */ #ifdef QUALITY int qualityFlag = 1; #else int qualityFlag = 0; #endif /*--------------------------------------------------------------*/ MPEGvideo::MPEGvideo(MPEGstream *stream) { Uint32 start_code; MPEGstream_marker *marker; /* Set the MPEG data stream */ mpeg = stream; time_source = NULL; /* Set default playback variables */ _thread = NULL; _dst = NULL; _mutex = NULL; _stream = NULL; /* Mark the data to leave the stream unchanged */ /* after parsing */ marker = mpeg->new_marker(0); /* Get the width and height of the video */ start_code = mpeg->copy_byte(); start_code <<= 8; start_code |= mpeg->copy_byte(); start_code <<= 8; start_code |= mpeg->copy_byte(); while ( ! mpeg->eof() && (start_code != SEQ_START_CODE) ) { start_code <<= 8; start_code |= mpeg->copy_byte(); } if ( start_code == SEQ_START_CODE ) { Uint8 buf[4]; /* Get the width and height of the video */ mpeg->copy_data(buf, 4); _w = (buf[0]<<4)|(buf[1]>>4); /* 12 bits of width */ _h = ((buf[1]&0xF)<<8)|buf[2]; /* 12 bits of height */ switch(buf[3]&0xF) /* 4 bits of fps */ { case 1: _fps = 23.97f; break; case 2: _fps = 24.00f; break; case 3: _fps = 25.00f; break; case 4: _fps = 29.97f; break; case 5: _fps = 30.00f; break; case 6: _fps = 50.00f; break; case 7: _fps = 59.94f; break; case 8: _fps = 60.00f; break; case 9: _fps = 15.00f; break; default: _fps = 30.00f; break; } } else { _w = 0; _h = 0; _fps = 0.00; SetError("Not a valid MPEG video stream"); } /* Rewind back to the old position */ mpeg->seek_marker(marker); mpeg->delete_marker(marker); /* Keep original width and height in _ow and _oh */ _ow = _w; _oh = _h; /* Now round up width and height to a multiple */ /* of a macroblock size (16 pixels) to keep the */ /* video decoder happy */ _w = (_w + 15) & ~15; _h = (_h + 15) & ~15; /* Set the default playback area */ _dstrect.x = 0; _dstrect.y = 0; _dstrect.w = 0; _dstrect.h = 0; /* Set the source area */ _srcrect.x = 0; _srcrect.y = 0; _srcrect.w = _ow; _srcrect.h = _oh; _image = 0; _filter = SMPEGfilter_null(); _filter_mutex = SDL_CreateMutex(); // printf("[MPEGvideo::MPEGvideo]_filter_mutex[%lx] = SDL_CreateMutex()\n",_filter_mutex); } MPEGvideo:: ~MPEGvideo() { /* Stop it before we free everything */ Stop(); /* Free actual video stream */ if( _stream ) DestroyVidStream( _stream ); /* Free overlay */ if(_image) SDL_FreeYUVOverlay(_image); /* Release filter */ SDL_DestroyMutex(_filter_mutex); _filter->destroy(_filter); } /* Simple thread play function */ int Play_MPEGvideo( void *udata ) { MPEGvideo *mpeg = (MPEGvideo *)udata; /* Get the time the playback started */ mpeg->_stream->realTimeStart += ReadSysClock(); #ifdef TIME_MPEG int start_frames, stop_frames; int total_frames; Uint32 start_time, stop_time; float total_time; start_frames = mpeg->_stream->totNumFrames; start_time = SDL_GetTicks(); #endif mpeg->force_exit = false; while( mpeg->playing && !mpeg->force_exit ) { int mark = mpeg->_stream->totNumFrames; /* make sure we do a whole frame */ while( (mark == mpeg->_stream->totNumFrames) && mpeg->playing && !mpeg->force_exit ) { mpegVidRsrc( 0, mpeg->_stream, 0 ); } if( mpeg->_stream->film_has_ended || mpeg->force_exit ) { mpeg->playing = false; } } /* Get the time the playback stopped */ mpeg->_stream->realTimeStart -= ReadSysClock(); #ifdef TIME_MPEG stop_time = SDL_GetTicks(); stop_frames = mpeg->_stream->totNumFrames; total_frames = (stop_frames-start_frames); total_time = (float)(stop_time-start_time)/1000.0; if ( total_time > 0 ) { printf("%d frames in %2.2f seconds (%2.2f FPS)\n", total_frames, total_time, (float)total_frames/total_time); } #endif return(0); } void MPEGvideo:: Play(void) { ResetPause(); if ( _stream ) { if ( playing ) { Stop(); } playing = true; #ifdef DISABLE_VIDEO_CALLBACK_THREAD Play_MPEGvideo(this); #else _thread = SDL_CreateThread( Play_MPEGvideo, this ); if ( !_thread ) { playing = false; } #endif } } void MPEGvideo:: Stop(void) { if ( _thread ) { force_exit = true; SDL_WaitThread(_thread, NULL); _thread = NULL; } playing = false; ResetPause(); } void MPEGvideo:: Rewind(void) { Stop(); if ( _stream ) { /* Reinitialize vid_stream pointers */ ResetVidStream( _stream ); #ifdef ANALYSIS init_stats(); #endif } } void MPEGvideo:: ResetSynchro(double time) { if( _stream ) { _stream->_jumpFrame = -1; _stream->realTimeStart = -time; play_time = time; if (time > 0) { double oneframetime; if (_stream->_oneFrameTime == 0) oneframetime = 1.0 / _stream->_smpeg->_fps; else oneframetime = _stream->_oneFrameTime; /* time -> frame */ _stream->totNumFrames = (int)(time / oneframetime); /* Set Current Frame To 0 & Frame Adjust Frag Set */ _stream->current_frame = 0; _stream->need_frameadjust=true; } } } void MPEGvideo::Skip(float seconds) { int frame; /* Called only when there is no timestamp info in the MPEG */ /* This is quite slow however */ printf("Video: Skipping %f seconds...\n", seconds); frame = (int) (_fps * seconds); if( _stream ) { _stream->_jumpFrame = frame; while( (_stream->totNumFrames < frame) && ! _stream->film_has_ended ) { mpegVidRsrc( 0, _stream, 0 ); } ResetSynchro(0); } } /* Michel Darricau from eProcess conflict name in popcorn */ MPEGstatus MPEGvideo:: GetStatus(void) { if ( _stream ) { if( !_thread || (_stream->film_has_ended ) ) { return MPEG_STOPPED; } else { return MPEG_PLAYING; } } return MPEG_ERROR; } bool MPEGvideo:: GetVideoInfo(MPEG_VideoInfo *info) { if ( info ) { info->width = _ow; info->height = _oh; if ( _stream ) { info->current_frame = _stream->current_frame; #ifdef CALCULATE_FPS /* Get the appropriate indices for the timestamps */ /* Calculate the frames-per-second from the timestamps */ if ( _stream->frame_time[_stream->timestamp_index] ) { double *timestamps; double time_diff; int this_index; int last_index; timestamps = _stream->frame_time; last_index = _stream->timestamp_index; this_index = last_index - 1; if ( this_index < 0 ) { this_index = FPS_WINDOW-1; } time_diff = timestamps[this_index] - timestamps[last_index]; info->current_fps = (double)FPS_WINDOW / time_diff; } else { info->current_fps = 0.0; } #else info->current_fps = _stream->totNumFrames / (ReadSysClock() - _stream->realTimeStart); #endif } else { info->current_frame = 0; info->current_fps = 0.0; } } return(!WasError()); } /* Returns zero if fails. surf - Surface to play movie on. lock - lock is held while MPEG stream is playing callback - called on every frame, for display update */ bool MPEGvideo:: SetDisplay(SDL_Surface *dst, SDL_mutex *lock, MPEG_DisplayCallback callback) { _mutex = lock; _dst = dst; _callback = callback; if ( _image ) { SDL_FreeYUVOverlay(_image); } _image = SDL_CreateYUVOverlay(_srcrect.w, _srcrect.h, SDL_YV12_OVERLAY, dst); if ( _image == NULL ) { return false; } if ( !_dstrect.w || !_dstrect.h ) { _dstrect.w = dst->w; _dstrect.h = dst->h; } if ( !_stream ) { decodeInitTables(); InitCrop(); InitIDCT(); _stream = NewVidStream( (unsigned int) BUF_LENGTH ); if( _stream ) { _stream->_smpeg = this; _stream->ditherType = FULL_COLOR_DITHER; _stream->matched_depth = dst->format->BitsPerPixel; if( mpegVidRsrc( 0, _stream, 1 ) == NULL ) { SetError("Not an MPEG video stream"); return false; } } if ( ! InitPictImages(_stream, _w, _h, _dst) ) return false; } return true; } /* If this is being called during play, the calling program is responsible for clearing the old area and coordinating with the update callback. */ void MPEGvideo:: MoveDisplay( int x, int y ) { SDL_mutexP( _mutex ); _dstrect.x = x; _dstrect.y = y; SDL_mutexV( _mutex ); } void MPEGvideo:: ScaleDisplayXY( int w, int h ) { SDL_mutexP( _mutex ); _dstrect.w = w; _dstrect.h = h; SDL_mutexV( _mutex ); } void MPEGvideo:: SetDisplayRegion(int x, int y, int w, int h) { SDL_mutexP( _mutex ); _srcrect.x = x; _srcrect.y = y; _srcrect.w = w; _srcrect.h = h; if(_image) { SDL_FreeYUVOverlay(_image); _image = SDL_CreateYUVOverlay(_srcrect.w, _srcrect.h, SDL_YV12_OVERLAY, _dst); /* !!! FIXME: Uhh...what if this one fails? */ } SDL_mutexV( _mutex ); } /* API CHANGE: This function no longer takes a destination surface and x/y You must use SetDisplay() and MoveDisplay() to set those attributes. */ void MPEGvideo:: RenderFrame( int frame ) { _stream->need_frameadjust = true; if( _stream->current_frame > frame ) { mpeg->rewind_stream(); mpeg->next_packet(); Rewind(); } _stream->_jumpFrame = frame; while( (_stream->current_frame < frame) && ! _stream->film_has_ended ) { mpegVidRsrc( 0, _stream, 0 ); } _stream->_jumpFrame = -1; } void MPEGvideo:: RenderFinal(SDL_Surface *dst, int x, int y) { SDL_Surface *saved_dst; int saved_x, saved_y; /* This operation can only be performed when stopped */ Stop(); /* Set (and save) the destination and location */ saved_dst = _dst; saved_x = _dstrect.x; saved_y = _dstrect.y; SetDisplay(dst, _mutex, _callback); MoveDisplay(x, y); if ( ! _stream->film_has_ended ) { /* Search for the last "group of pictures" start code */ Uint32 start_code; MPEGstream_marker * marker, * oldmarker; marker = 0; start_code = mpeg->copy_byte(); start_code <<= 8; start_code |= mpeg->copy_byte(); start_code <<= 8; start_code |= mpeg->copy_byte(); while ( ! mpeg->eof() ) { start_code <<= 8; start_code |= mpeg->copy_byte(); if ( start_code == GOP_START_CODE ) { oldmarker = marker; marker = mpeg->new_marker(-4); if( oldmarker ) mpeg->delete_marker( oldmarker ); mpeg->garbage_collect(); } } /* Set the stream to the last spot marked */ if ( ! mpeg->seek_marker( marker ) ) { mpeg->rewind_stream(); mpeg->next_packet(); } mpeg->delete_marker( marker ); _stream->buf_length = 0; _stream->bit_offset = 0; /* Process all frames without displaying any */ _stream->_skipFrame = 1; RenderFrame( INT_MAX ); mpeg->garbage_collect(); } /* Display the frame */ DisplayFrame(_stream); /* Restore the destination and location */ SetDisplay(saved_dst, _mutex, _callback); MoveDisplay(saved_x, saved_y); } /* EOF */