📄 video.c
字号:
rb->lcd_puts(0, 3, "Any Other = no"); rb->lcd_puts(0, 4, " (plays from start)"); DrawPosition(gFileHdr.resume_pos, rb->filesize(fd)); rb->lcd_update(); button = WaitForButton(); return (button == BUTTON_PLAY);}int SeekTo(int fd, int nPos){ int read_now, got_now; if (gPlay.bHasAudio) rb->mp3_play_stop(); // stop audio ISR if (gPlay.bHasVideo) timer_set(0); // stop the timer 4 rb->lseek(fd, nPos, SEEK_SET); gBuf.pBufFill = gBuf.pBufStart; // all empty gBuf.pReadVideo = gBuf.pReadAudio = gBuf.pBufStart; read_now = (PRECHARGE + gBuf.granularity - 1); // round up read_now -= read_now % gBuf.granularity; // to granularity got_now = rb->read(fd, gBuf.pBufFill, read_now); gBuf.bEOF = (read_now != got_now); gBuf.pBufFill += got_now; if (nPos == 0) { // we seeked to the start if (gPlay.bHasVideo) gBuf.pReadVideo += gFileHdr.video_1st_frame; if (gPlay.bHasAudio) gBuf.pReadAudio += gFileHdr.audio_1st_frame; } else { // we have to search for the positions if (gPlay.bHasAudio) // prepare audio playback, if contained { // search for audio frame while (((tAudioFrameHeader*)(gBuf.pReadAudio))->magic != AUDIO_MAGIC) gBuf.pReadAudio += gFileHdr.blocksize; if (gPlay.bHasVideo) SyncVideo(); // pick the right video for that } } // synchronous start gPlay.state = playing; if (gPlay.bHasAudio) { gPlay.bAudioUnderrun = false; rb->mp3_play_data(gBuf.pReadAudio + gFileHdr.audio_headersize, gFileHdr.blocksize - gFileHdr.audio_headersize, GetMoreMp3); rb->mp3_play_pause(true); // kickoff audio } if (gPlay.bHasVideo) { gPlay.bVideoUnderrun = false; timer_set(gFileHdr.video_frametime); // start display interrupt } return 0;}// returns >0 if continue, =0 to stop, <0 to abort (USB)int PlayTick(int fd){ int button; int avail_audio = -1, avail_video = -1; int retval = 1; int filepos; // check buffer level if (gPlay.bHasAudio) avail_audio = Available(gBuf.pReadAudio); if (gPlay.bHasVideo) avail_video = Available(gBuf.pReadVideo); if ((gPlay.bHasAudio && avail_audio < gBuf.low_water) || (gPlay.bHasVideo && avail_video < gBuf.low_water)) { gPlay.bRefilling = true; /* go to refill mode */ } if ((!gPlay.bHasAudio || gPlay.bAudioUnderrun) && (!gPlay.bHasVideo || gPlay.bVideoUnderrun) && gBuf.bEOF) { if (gFileHdr.resume_pos) { // we played til the end, clear resume position gFileHdr.resume_pos = 0; rb->lseek(fd, 0, SEEK_SET); // save resume position rb->write(fd, &gFileHdr, sizeof(gFileHdr)); } return 0; // all expired } if (!gPlay.bRefilling || gBuf.bEOF) { // nothing to do button = rb->button_get_w_tmo(HZ/10); } else { // refill buffer int read_now, got_now; int buf_free; // how much can we reload, don't fill completely, would appear empty buf_free = gBuf.bufsize - MAX(avail_audio, avail_video) - gBuf.high_water; if (buf_free < 0) buf_free = 0; // just for safety buf_free -= buf_free % gBuf.granularity; // round down to granularity // in one piece max. up to buffer end (wrap after that) read_now = MIN(buf_free, gBuf.pBufEnd - gBuf.pBufFill); // load piecewise, to stay responsive read_now = MIN(read_now, gBuf.nReadChunk); if (read_now == buf_free) gPlay.bRefilling = false; // last piece requested got_now = rb->read(fd, gBuf.pBufFill, read_now); if (got_now != read_now || read_now == 0) { gBuf.bEOF = true; gPlay.bRefilling = false; } if (!gPlay.bRefilling && rb->global_settings->disk_spindown < 20) // condition for test only { rb->ata_sleep(); // no point in leaving the disk run til timeout } gBuf.pBufFill += got_now; if (gBuf.pBufFill >= gBuf.pBufEnd) gBuf.pBufFill = gBuf.pBufStart; // wrap rb->yield(); // have mercy with the other threads button = rb->button_get(false); } // check keypresses if (button != BUTTON_NONE) { filepos = rb->lseek(fd, 0, SEEK_CUR); if (gPlay.bHasVideo) // video position is more accurate filepos -= Available(gBuf.pReadVideo); // take video position else filepos -= Available(gBuf.pReadAudio); // else audio switch (button) { // set exit conditions case BUTTON_OFF: if (gFileHdr.magic == HEADER_MAGIC // only if file has header && !(gFileHdr.flags & FLAG_LOOP)) // not for stills { gFileHdr.resume_pos = filepos; rb->lseek(fd, 0, SEEK_SET); // save resume position rb->write(fd, &gFileHdr, sizeof(gFileHdr)); } retval = 0; // signal "stop" to caller break; case SYS_USB_CONNECTED: retval = -1; // signal "abort" to caller break; case BUTTON_PLAY: if (gPlay.bSeeking) { gPlay.bSeeking = false; gPlay.state = playing; SeekTo(fd, gPlay.nSeekPos); } else if (gPlay.state == playing) { gPlay.state = paused; if (gPlay.bHasAudio) rb->mp3_play_pause(false); // pause audio if (gPlay.bHasVideo) and_b(~0x10, &TSTR); // stop the timer 4 } else if (gPlay.state == paused) { gPlay.state = playing; if (gPlay.bHasAudio) { if (gPlay.bHasVideo) SyncVideo(); rb->mp3_play_pause(true); // play audio } if (gPlay.bHasVideo) or_b(0x10, &TSTR); // start the video } break; case BUTTON_UP: case BUTTON_UP | BUTTON_REPEAT: if (gPlay.bHasAudio) ChangeVolume(1); break; case BUTTON_DOWN: case BUTTON_DOWN | BUTTON_REPEAT: if (gPlay.bHasAudio) ChangeVolume(-1); break; case BUTTON_LEFT: case BUTTON_LEFT | BUTTON_REPEAT: if (!gPlay.bSeeking) // prepare seek { gPlay.nSeekPos = filepos; gPlay.bSeeking = true; gPlay.nSeekAcc = 0; } else if (gPlay.nSeekAcc > 0) // other direction, stop sliding gPlay.nSeekAcc = 0; else gPlay.nSeekAcc--; break; case BUTTON_RIGHT: case BUTTON_RIGHT | BUTTON_REPEAT: if (!gPlay.bSeeking) // prepare seek { gPlay.nSeekPos = filepos; gPlay.bSeeking = true; gPlay.nSeekAcc = 0; } else if (gPlay.nSeekAcc < 0) // other direction, stop sliding gPlay.nSeekAcc = 0; else gPlay.nSeekAcc++; break; case BUTTON_F1: // debug key case BUTTON_F1 | BUTTON_REPEAT: DrawBuf(); // show buffer status gPlay.nTimeOSD = 30; gPlay.bDirtyOSD = true; break; } } /* if (button != BUTTON_NONE) */ // handle seeking if (gPlay.bSeeking) // seeking? { if (gPlay.nSeekAcc < -MAX_ACC) gPlay.nSeekAcc = -MAX_ACC; else if (gPlay.nSeekAcc > MAX_ACC) gPlay.nSeekAcc = MAX_ACC; gPlay.nSeekPos += gPlay.nSeekAcc * gBuf.nSeekChunk; if (gPlay.nSeekPos < 0) gPlay.nSeekPos = 0; if (gPlay.nSeekPos > rb->filesize(fd) - gBuf.granularity) { gPlay.nSeekPos = rb->filesize(fd); gPlay.nSeekPos -= gPlay.nSeekPos % gBuf.granularity; } DrawPosition(gPlay.nSeekPos, rb->filesize(fd)); } // check + recover underruns if ((gPlay.bAudioUnderrun || gPlay.bVideoUnderrun) && !gBuf.bEOF) { filepos = rb->lseek(fd, 0, SEEK_CUR); if (gPlay.bHasVideo && gPlay.bVideoUnderrun) { gStats.nVideoUnderruns++; filepos -= Available(gBuf.pReadVideo); // take video position SeekTo(fd, filepos); } else if (gPlay.bHasAudio && gPlay.bAudioUnderrun) { gStats.nAudioUnderruns++; filepos -= Available(gBuf.pReadAudio); // else audio SeekTo(fd, filepos); } } return retval;}int main(char* filename){ int file_size; int fd; /* file descriptor handle */ int read_now, got_now; int button = 0; int retval; // try to open the file fd = rb->open(filename, O_RDWR); if (fd < 0) return PLUGIN_ERROR; file_size = rb->filesize(fd); // init statistics rb->memset(&gStats, 0, sizeof(gStats)); gStats.minAudioAvail = gStats.minVideoAvail = INT_MAX; // init playback state rb->memset(&gPlay, 0, sizeof(gPlay)); // init buffer rb->memset(&gBuf, 0, sizeof(gBuf)); gBuf.pOSD = rb->lcd_framebuffer + LCD_WIDTH*7; // last screen line gBuf.pBufStart = rb->plugin_get_mp3_buffer(&gBuf.bufsize); //gBuf.bufsize = 1700*1024; // test, like 2MB version!!!! gBuf.pBufFill = gBuf.pBufStart; // all empty // load file header read_now = sizeof(gFileHdr); got_now = rb->read(fd, &gFileHdr, read_now); rb->lseek(fd, 0, SEEK_SET); // rewind to restart sector-aligned if (got_now != read_now) { rb->close(fd); return (PLUGIN_ERROR); } // check header if (gFileHdr.magic != HEADER_MAGIC) { // old file, use default info rb->memset(&gFileHdr, 0, sizeof(gFileHdr)); gFileHdr.blocksize = SCREENSIZE; if (file_size < SCREENSIZE * FPS) // less than a second gFileHdr.flags |= FLAG_LOOP; gFileHdr.video_format = VIDEOFORMAT_RAW; gFileHdr.video_width = LCD_WIDTH; gFileHdr.video_height = LCD_HEIGHT; gFileHdr.video_frametime = CLOCK / FPS; gFileHdr.bps_peak = gFileHdr.bps_average = LCD_WIDTH * LCD_HEIGHT * FPS; } // continue buffer init: align the end, calc low water, read sizes gBuf.granularity = gFileHdr.blocksize; while (gBuf.granularity % 512) // common multiple of sector size gBuf.granularity *= 2; gBuf.bufsize -= gBuf.bufsize % gBuf.granularity; // round down gBuf.pBufEnd = gBuf.pBufStart + gBuf.bufsize; gBuf.low_water = SPINUP * gFileHdr.bps_peak / 8000; if (gFileHdr.audio_min_associated < 0) gBuf.high_water = 0 - gFileHdr.audio_min_associated; else gBuf.high_water = 1; // never fill buffer completely, would appear empty gBuf.nReadChunk = (CHUNK + gBuf.granularity - 1); // round up gBuf.nReadChunk -= gBuf.nReadChunk % gBuf.granularity;// and align gBuf.nSeekChunk = rb->filesize(fd) / FF_TICKS; gBuf.nSeekChunk += gBuf.granularity - 1; // round up gBuf.nSeekChunk -= gBuf.nSeekChunk % gBuf.granularity; // and align // prepare video playback, if contained if (gFileHdr.video_format == VIDEOFORMAT_RAW) { gPlay.bHasVideo = true; if (rb->global_settings->backlight_timeout > 0) rb->backlight_set_timeout(1); // keep the light on } // prepare audio playback, if contained if (gFileHdr.audio_format == AUDIOFORMAT_MP3_BITSWAPPED) { gPlay.bHasAudio = true; } // start playback by seeking to zero or resume position if (gFileHdr.resume_pos && WantResume(fd)) // ask the user SeekTo(fd, gFileHdr.resume_pos); else SeekTo(fd, 0); // all that's left to do is keep the buffer full do // the main loop { retval = PlayTick(fd); } while (retval > 0); rb->close(fd); // close the file if (gPlay.bHasVideo) timer_set(0); // stop video ISR, now I can use the display again if (gPlay.bHasAudio) rb->mp3_play_stop(); // stop audio ISR // restore normal backlight setting rb->backlight_set_timeout(rb->global_settings->backlight_timeout); if (retval < 0) // aborted? { return PLUGIN_USB_CONNECTED; }#ifndef DEBUG // for release compilations, only display the stats in case of error if (gStats.nAudioUnderruns || gStats.nVideoUnderruns)#endif { // display statistics rb->lcd_clear_display(); rb->snprintf(gPrint, sizeof(gPrint), "%d Audio Underruns", gStats.nAudioUnderruns); rb->lcd_puts(0, 0, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "%d Video Underruns", gStats.nVideoUnderruns); rb->lcd_puts(0, 1, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "%d MinAudio bytes", gStats.minAudioAvail); rb->lcd_puts(0, 2, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "%d MinVideo bytes", gStats.minVideoAvail); rb->lcd_puts(0, 3, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "ReadChunk: %d", gBuf.nReadChunk); rb->lcd_puts(0, 4, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "SeekChunk: %d", gBuf.nSeekChunk); rb->lcd_puts(0, 5, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "LowWater: %d", gBuf.low_water); rb->lcd_puts(0, 6, gPrint); rb->snprintf(gPrint, sizeof(gPrint), "HighWater: %d", gBuf.high_water); rb->lcd_puts(0, 7, gPrint); rb->lcd_update(); button = WaitForButton(); } return (button == SYS_USB_CONNECTED) ? PLUGIN_USB_CONNECTED : PLUGIN_OK;}/***************** Plugin Entry Point *****************/enum plugin_status plugin_start(struct plugin_api* api, void* parameter){ int ret; /* this macro should be called as the first thing you do in the plugin. it test that the api version and model the plugin was compiled for matches the machine it is running on */ TEST_PLUGIN_API(api); rb = api; // copy to global api pointer if (parameter == NULL) { rb->splash(HZ*2, true, "Play .rvf file!"); return PLUGIN_ERROR; } // now go ahead and have fun! ret = main((char*) parameter); if (ret==PLUGIN_USB_CONNECTED) rb->usb_screen(); return ret;}#endif // #ifdef HAVE_LCD_BITMAP#endif // #ifndef SIMULATOR
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -