📄 video.c
字号:
/**************************************************************************** __________ __ ___.* Open \______ \ ____ ____ | | _\_ |__ _______ ___* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \* \/ \/ \/ \/ \/* $Id: video.c,v 1.15 2004/03/08 20:14:04 hohensoh Exp $** Plugin for video playback* Reads raw image data + audio data from a file* !!!!!!!!!! Code Police free zone !!!!!!!!!!** Copyright (C) 2003-2004 J鰎g Hohensohn aka [IDC]Dragon** All files in this archive are subject to the GNU General Public License.* See the file COPYING in the source tree root for full license agreement.** This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY* KIND, either express or implied.*****************************************************************************//****************** imports ******************/#include "plugin.h"#include "sh7034.h"#include "system.h"#include "../apps/recorder/widgets.h" // not in search path, booh#ifndef SIMULATOR // not for simulator by now#ifdef HAVE_LCD_BITMAP // and definitely not for the Player, haha/****************** constants ******************/#define IMIA4 (*((volatile unsigned long*)0x09000180)) // timer 4#define INT_MAX ((int)(~(unsigned)0 >> 1))#define INT_MIN (-INT_MAX-1)#define SCREENSIZE (LCD_WIDTH*LCD_HEIGHT/8) // in bytes#define FPS 68 // default fps for headerless (old video-only) file#define MAX_ACC 20 // maximum FF/FR speedup#define FF_TICKS 3000; // experimentally found nice// trigger levels, we need about 80 kB/sec#define PRECHARGE (1024 * 64) // the initial filling before starting to play#define SPINUP 3300 // from what level on to refill, in milliseconds#define CHUNK (1024*32) // read size/****************** prototypes ******************/void timer_set(unsigned period); // setup ISR and timer registersvoid timer4_isr(void) __attribute__((interrupt_handler)); // IMIA4 ISRint check_button(void); // determine next relative frame/****************** data types ******************/// plugins don't introduce headers, so structs are repeated from rvf_format.h#define HEADER_MAGIC 0x52564668 // "RVFh" at file start #define AUDIO_MAGIC 0x41756446 // "AudF" for each audio block#define FILEVERSION 100 // 1.00#define CLOCK 11059200 // SH CPU clock// format type definitions#define VIDEOFORMAT_NO_VIDEO 0#define VIDEOFORMAT_RAW 1#define AUDIOFORMAT_NO_AUDIO 0#define AUDIOFORMAT_MP3 1#define AUDIOFORMAT_MP3_BITSWAPPED 2// bit flags#define FLAG_LOOP 0x00000001 // loop the playback, e.g. for stillstypedef struct // contains whatever might be useful to the player{ // general info (16 entries = 64 byte) unsigned long magic; // HEADER_MAGIC unsigned long version; // file version unsigned long flags; // combination of FLAG_xx unsigned long blocksize; // how many bytes per block (=video frame) unsigned long bps_average; // bits per second of the whole stream unsigned long bps_peak; // max. of above (audio may be VBR) unsigned long resume_pos; // file position to resume to unsigned long reserved[9]; // reserved, should be zero // video info (16 entries = 64 byte) unsigned long video_format; // one of VIDEOFORMAT_xxx unsigned long video_1st_frame; // byte position of first video frame unsigned long video_duration; // total length of video part, in ms unsigned long video_payload_size; // total amount of video data, in bytes unsigned long video_bitrate; // derived from resolution and frame time, in bps unsigned long video_frametime; // frame interval in 11.0592 MHz clocks long video_preroll; // video is how much ahead, in 11.0592 MHz clocks unsigned long video_width; // in pixels unsigned long video_height; // in pixels unsigned long video_reserved[7]; // reserved, should be zero // audio info (16 entries = 64 byte) unsigned long audio_format; // one of AUDIOFORMAT_xxx unsigned long audio_1st_frame; // byte position of first video frame unsigned long audio_duration; // total length of audio part, in ms unsigned long audio_payload_size; // total amount of audio data, in bytes unsigned long audio_avg_bitrate; // average audio bitrate, in bits per second unsigned long audio_peak_bitrate; // maximum bitrate unsigned long audio_headersize; // offset to payload in audio frames long audio_min_associated; // minimum offset to video frame, in bytes long audio_max_associated; // maximum offset to video frame, in bytes unsigned long audio_reserved[7]; // reserved, should be zero // more to come... ? // Note: padding up to 'blocksize' with zero following this header} tFileHeader;typedef struct // the little header for all audio blocks{ unsigned long magic; // AUDIO_MAGIC indicates an audio block unsigned char previous_block; // previous how many blocks backwards unsigned char next_block; // next how many blocks forward short associated_video; // offset to block with corresponding video unsigned short frame_start; // offset to first frame starting in this block unsigned short frame_end; // offset to behind last frame ending in this block} tAudioFrameHeader;/****************** globals ******************/static struct plugin_api* rb; /* here is a global api struct pointer */static char gPrint[32]; /* a global printf buffer, saves stack */// playstatestatic struct { enum { paused, playing, } state; bool bAudioUnderrun; bool bVideoUnderrun; bool bHasAudio; bool bHasVideo; int nTimeOSD; // OSD should stay for this many frames bool bDirtyOSD; // OSD needs redraw bool bRefilling; // set if refilling buffer bool bSeeking; int nSeekAcc; // accelleration value for seek int nSeekPos; // current file position for seek} gPlay;// buffer informationstatic struct{ int bufsize; int granularity; // common multiple of block and sector size unsigned char* pBufStart; // start of ring buffer unsigned char* pBufEnd; // end of ring buffer unsigned char* pOSD; // OSD memory (112 bytes for 112*8 pixels) int vidcount; // how many video blocks are known in a row unsigned char* pBufFill; // write pointer for disk, owned by main task unsigned char* pReadVideo; // video readout, maintained by timer ISR unsigned char* pReadAudio; // audio readout, maintained by demand ISR bool bEOF; // flag for end of file int low_water; // reload threshold int high_water; // end of reload threshold int nReadChunk; // how much data for normal buffer fill int nSeekChunk; // how much data while seeking} gBuf;// statisticsstatic struct{ int minAudioAvail; int minVideoAvail; int nAudioUnderruns; int nVideoUnderruns;} gStats;tFileHeader gFileHdr; // file header/****************** implementation ******************/// tool function: return how much playable audio/video is leftint Available(unsigned char* pSnapshot){ if (pSnapshot <= gBuf.pBufFill) return gBuf.pBufFill - pSnapshot; else return gBuf.bufsize - (pSnapshot - gBuf.pBufFill);}// debug function to draw buffer indicatorsvoid DrawBuf(void){ int fill, video, audio; rb->memset(gBuf.pOSD, 0x10, LCD_WIDTH); // draw line gBuf.pOSD[0] = gBuf.pOSD[LCD_WIDTH-1] = 0xFE; // ends // calculate new tick positions fill = 1 + ((gBuf.pBufFill - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; video = 1 + ((gBuf.pReadVideo - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; audio = 1 + ((gBuf.pReadAudio - gBuf.pBufStart) * (LCD_WIDTH-2)) / gBuf.bufsize; gBuf.pOSD[fill] |= 0x20; // below the line, two pixels gBuf.pOSD[video] |= 0x08; // one above gBuf.pOSD[audio] |= 0x04; // two above if (gPlay.state == paused) // we have to draw ourselves rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); else gPlay.bDirtyOSD = true; // redraw it with next timer IRQ}// helper function to draw a position indicatorvoid DrawPosition(int pos, int total){ int w,h; int sec; // estimated seconds int percent; /* print the estimated position */ sec = pos / (gFileHdr.bps_average/8); if (sec < 100*60) /* fits into mm:ss format */ rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dm", sec/60, sec%60); else /* a very long clip, hh:mm format */ rb->snprintf(gPrint, sizeof(gPrint), "%02d:%02dh", sec/3600, (sec/60)%60); rb->lcd_puts(0, 7, gPrint); /* draw a slider over the rest of the line */ rb->lcd_getstringsize(gPrint, &w, &h); w++; percent = pos/(total/100); rb->slidebar(w, LCD_HEIGHT-7, LCD_WIDTH-w, 7, percent, Grow_Right); if (gPlay.state == paused) // we have to draw ourselves rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); else // let the display time do it { gPlay.nTimeOSD = 70; gPlay.bDirtyOSD = true; // redraw it with next timer IRQ }}// helper function to change the volume by a certain amount, +/-void ChangeVolume(int delta){ int vol = rb->global_settings->volume + delta; if (vol > 100) vol = 100; else if (vol < 0) vol = 0; if (vol != rb->global_settings->volume) { rb->mpeg_sound_set(SOUND_VOLUME, vol); rb->global_settings->volume = vol; rb->snprintf(gPrint, sizeof(gPrint), "Vol: %d", vol); rb->lcd_puts(0, 7, gPrint); if (gPlay.state == paused) // we have to draw ourselves rb->lcd_update_rect(0, LCD_HEIGHT-8, LCD_WIDTH, 8); else // let the display time do it { gPlay.nTimeOSD = 50; // display it for 50 frames gPlay.bDirtyOSD = true; // let the refresh copy it to LCD } }}// sync the video to the current audiovoid SyncVideo(void){ tAudioFrameHeader* pAudioBuf; pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio); if (pAudioBuf->magic == AUDIO_MAGIC) { gBuf.vidcount = 0; // nothing known // sync the video position gBuf.pReadVideo = gBuf.pReadAudio + (long)pAudioBuf->associated_video * (long)gFileHdr.blocksize; // handle possible wrap if (gBuf.pReadVideo >= gBuf.pBufEnd) gBuf.pReadVideo -= gBuf.bufsize; else if (gBuf.pReadVideo < gBuf.pBufStart) gBuf.pReadVideo += gBuf.bufsize; }}// setup ISR and timer registersvoid timer_set(unsigned period){ if (period) { and_b(~0x10, &TSTR); // Stop the timer 4 and_b(~0x10, &TSNC); // No synchronization and_b(~0x10, &TMDR); // Operate normally IMIA4 = (unsigned long)timer4_isr; // install ISR TSR4 &= ~0x01; TIER4 = 0xF9; // Enable GRA match interrupt GRA4 = (unsigned short)(period/4 - 1); TCR4 = 0x22; // clear at GRA match, sysclock/4 IPRD = (IPRD & 0xFF0F) | 0x0010; // interrupt priority 1 (lowest) or_b(0x10, &TSTR); // start timer 4 } else { and_b(~0x10, &TSTR); // stop the timer 4 IPRD = (IPRD & 0xFF0F); // disable interrupt }}// timer interrupt handler to display a framevoid timer4_isr(void) // IMIA4{ int available; tAudioFrameHeader* pAudioBuf; int height; // height to display TSR4 &= ~0x01; // clear the interrupt // xor_b(0x40, &PBDRL); // test: toggle LED (PB6) // debug code/* gPlay.nTimeOSD = 1; DrawBuf(); gPlay.bDirtyOSD = true;*/ // reduce height if we have OSD on height = gFileHdr.video_height/8; if (gPlay.nTimeOSD > 0) { gPlay.nTimeOSD--; height = MIN(LCD_HEIGHT/8-1, height); // reserve bottom line if (gPlay.bDirtyOSD) { // OSD to bottom line rb->lcd_blit(gBuf.pOSD, 0, LCD_HEIGHT/8-1, LCD_WIDTH, 1, LCD_WIDTH); gPlay.bDirtyOSD = false; } } rb->lcd_blit(gBuf.pReadVideo, 0, 0, gFileHdr.video_width, height, gFileHdr.video_width); available = Available(gBuf.pReadVideo); // loop to skip audio frame(s) while(1) { // just for the statistics if (!gBuf.bEOF && available < gStats.minVideoAvail) gStats.minVideoAvail = available; if (available <= (int)gFileHdr.blocksize) { // no data for next frame if (gBuf.bEOF && (gFileHdr.flags & FLAG_LOOP)) { // loop now, assuming the looped clip fits in memory gBuf.pReadVideo = gBuf.pBufStart + gFileHdr.video_1st_frame; // FixMe: pReadVideo is incremented below } else { gPlay.bVideoUnderrun = true; timer_set(0); // disable ourselves return; // no data available } } else // normal advance for next time { gBuf.pReadVideo += gFileHdr.blocksize; if (gBuf.pReadVideo >= gBuf.pBufEnd) gBuf.pReadVideo -= gBuf.bufsize; // wraparound available -= gFileHdr.blocksize; } if (!gPlay.bHasAudio) break; // no need to skip any audio if (gBuf.vidcount) { // we know the next is a video frame gBuf.vidcount--; break; // exit the loop } pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadVideo); if (pAudioBuf->magic == AUDIO_MAGIC) { // we ran into audio, can happen after seek gBuf.vidcount = pAudioBuf->next_block; if (gBuf.vidcount) gBuf.vidcount--; // minus the audio block } } // while}// ISR function to get more mp3 datavoid GetMoreMp3(unsigned char** start, int* size){ int available; int advance; tAudioFrameHeader* pAudioBuf = (tAudioFrameHeader*)(gBuf.pReadAudio); advance = pAudioBuf->next_block * gFileHdr.blocksize; available = Available(gBuf.pReadAudio); // just for the statistics if (!gBuf.bEOF && available < gStats.minAudioAvail) gStats.minAudioAvail = available; if (available < advance + (int)gFileHdr.blocksize || advance == 0) { gPlay.bAudioUnderrun = true; return; // no data available } gBuf.pReadAudio += advance; if (gBuf.pReadAudio >= gBuf.pBufEnd) gBuf.pReadAudio -= gBuf.bufsize; // wraparound *start = gBuf.pReadAudio + gFileHdr.audio_headersize; *size = gFileHdr.blocksize - gFileHdr.audio_headersize;}int WaitForButton(void){ int button; do { button = rb->button_get(true); } while ((button & BUTTON_REL) && button != SYS_USB_CONNECTED); return button;}bool WantResume(int fd){ int button; rb->lcd_puts(0, 0, "Resume to this"); rb->lcd_puts(0, 1, "last position?"); rb->lcd_puts(0, 2, "PLAY = yes");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -