/* * ivtvplay * * This is a simple program to play MPEG files in conjunction with the iTVC15 * driver. It allows seeking to arbitrary GOPs by timestamp and runs a sync * thread to show frames as they are played with cycle-accurate timestamps. * * It is intended as a reference and testing implementation. * * Copyright (c) 2003 Matt T. Yourst * This program is licensed under the GNU General Public License, version 2 */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#undef _FILE_OFFSET_BITS //#include //#define _FILE_OFFSET_BITS 64 #include #include //__NR_sendfile #include #include #include #include #include #include #include #include #include typedef unsigned long W32; typedef unsigned long long W64; static inline W64 rdtsc() { W64 t; asm volatile("rdtsc" : "=A" (t)); return t; } inline int bit(unsigned int v, unsigned int i) { return (v >> i) & 1; } typedef union { struct { uint32_t padding:5, broken:1, closed:1, frame:6, second:6, padding2:1, minute:6, hour:5, drop:1; }; struct { uint32_t data; }; } gop_header_t; struct mpeg_index_entry { unsigned int frame; gop_header_t timestamp; unsigned long long offset; }; struct mpeg_file { struct mpeg_index_entry* index; int index_count; int mpegfd; W64 mpeg_file_size; W64 offset; }; struct ivtv_dec_timing_info { int frame; int ptslo; int ptshi; int scrlo; int scrhi; }; #define IVTV_API_DEC_GET_TIMING 0x00000015 #define IVTV_API_DEC_START_PLAYBACK 0x00000001 #define IVTV_API_DEC_STOP_PLAYBACK 0x00000002 #define IVTV_API_DEC_PAUSE_PLAYBACK 0x0000000d char* timestamp_to_string(char* str, gop_header_t timestamp) { static char buf[256]; char* p = (str) ? str : buf; snprintf(p, sizeof(buf), "%d:%02d:%02d:%d", timestamp.hour, timestamp.minute, timestamp.second, timestamp.frame); return p; } gop_header_t pts_to_timestamp(W32 pts, float fps) { gop_header_t gop; // MPEG "system clock reference" (SCR) and "presentation timestamp" (PTS) run at 90 KHz (90000 ticks per second): #define MPEG_CLOCK_FREQ 90000 int seconds = pts / MPEG_CLOCK_FREQ; int fracsec = pts % MPEG_CLOCK_FREQ; int minutes = seconds / 60; seconds = seconds % 60; int hours = minutes / 60; minutes = minutes % 60; gop.hour = hours; gop.minute = minutes; gop.second = seconds; gop.frame = (int)ceilf(((float)fracsec / (float)MPEG_CLOCK_FREQ) * fps); return gop; } char* pts_to_string(char* str, unsigned long pts) { static char buf[256]; char* p = (str) ? str : buf; int seconds = pts / MPEG_CLOCK_FREQ; int fracsec = pts % MPEG_CLOCK_FREQ; int minutes = seconds / 60; seconds = seconds % 60; int hours = minutes / 60; minutes = minutes % 60; float fps = 30; int frame = (int)ceilf(((float)fracsec / (float)MPEG_CLOCK_FREQ) * fps); snprintf(p, sizeof(buf), "%d:%02d:%02d:%d", hours, minutes, seconds, frame); return p; } static inline gop_header_t make_timestamp(int hour, int minute, int second, int frame) { gop_header_t gop; gop.hour = hour; gop.minute = minute; gop.second = second; gop.frame = frame; return gop; } int mpeg_open(struct mpeg_file* mpeg, char* filename) { int mpegfd; int indexfd; char indexfilename[PATH_MAX]; snprintf(indexfilename, sizeof(indexfilename), "%s.index", filename); if ((mpegfd = open(filename, O_RDONLY | O_LARGEFILE)) < 0) { fprintf(stderr, "Failed to open %s: %s\n", filename, strerror(errno)); return -2; } mpeg->mpegfd = mpegfd; mpeg->mpeg_file_size = lseek64(mpegfd, 0, SEEK_END); lseek64(mpegfd, 0, SEEK_SET); if ((indexfd = open(indexfilename, O_RDONLY | O_LARGEFILE)) < 0) { fprintf(stderr, "Failed to open %s.index: %s\n", filename, strerror(errno)); return -3; } W64 index_size = lseek64(indexfd, 0, SEEK_END); if (index_size < 0) { fprintf(stderr, "Failed to get size of %s.index: %s\n", filename, strerror(errno)); return -4; } mpeg->index = (struct mpeg_index_entry*)mmap(NULL, (W32)index_size, PROT_READ, MAP_PRIVATE, indexfd, 0); if (!mpeg->index) { fprintf(stderr, "Failed to map %s.index: %s\n", filename, strerror(errno)); return -5; } close(indexfd); mpeg->index_count = (W32)index_size / sizeof(struct mpeg_index_entry); printf("Index of %d GOPs:\n", mpeg->index_count); for (int i = 0; i < mpeg->index_count; i++) { //W64 delta = mpeg->index[i+1].offset - mpeg->index[i].offset; //printf(" [% 10d] %-15s (frame % 15d) @ % 20lld (gopsize %d)\n", i, timestamp_to_string(NULL, mpeg->index[i].timestamp), mpeg->index[i].frame, mpeg->index[i].offset, delta); #define MARK_GOP_START 6 //printf("INSERT INTO recordedmarkup (chanid, starttime, mark, type, offset) values ('1011', '20030808233000', %d, %d, \"%lld\");\n", (mpeg->index[i].frame / 15), MARK_GOP_START, mpeg->index[i].offset); ///video/1011_20030808233000_20030809000000.nuv.index } mpeg->offset = 0; return 0; } struct mpeg_index_entry* mpeg_find_nearest(struct mpeg_file* mpeg, const gop_header_t gop) { printf("mpeg_find_nearest: gop = %d\n", gop.data); int lo = 0; int hi = mpeg->index_count-1; int i = (hi + lo) / 2; for (;;) { if (hi == i) return &mpeg->index[i]; i = (lo + hi) / 2; if (gop.data > mpeg->index[i].timestamp.data) lo = i+1; else if (gop.data < mpeg->index[i].timestamp.data) { hi = i-1; } else { return &mpeg->index[i]; } } // (should never be reached) return NULL; } int mpeg_seek_to_time(struct mpeg_file* mpeg, const gop_header_t gop) { struct mpeg_index_entry* idx = mpeg_find_nearest(mpeg, gop); //printf("Searching for timestamp %d:%02d:%02d:%d: found GOP for timestamp %d:%02d:%02d:%d at file offset %lld.\n", //hour, minute, second, frame, idx->timestamp.hour, idx->timestamp.minute, idx->timestamp.second, idx->timestamp.frame, idx->offset); mpeg->offset = idx->offset; return 0; } int writeall(int fd, char* buf, int count) { int origcount = count; while (count > 0) { int n = write(fd, buf, count); if (n < 0) return n; count -= n; buf += n; } return origcount; } int streamfd(int fdout, int fdin, int count) { static const int bufsize = 262144; char buf[bufsize]; int remaining = count; count = 0; while (remaining > 0) { int n = (remaining < bufsize) ? remaining : bufsize; n = read(fdin, buf, n); if (n < 0) return n; int k = writeall(fdout, buf, n); if (k < 0) return k; assert(k == n); remaining -= n; count += n; } return count; } int mpeg_play(struct mpeg_file* mpeg, int videofd, const gop_header_t start_timestamp, const gop_header_t end_timestamp) { static const int bufsize = 65536; char* buf = new char[bufsize]; struct mpeg_index_entry* idx = mpeg_find_nearest(mpeg, start_timestamp); struct mpeg_index_entry* end_idx = mpeg_find_nearest(mpeg, end_timestamp); printf("mpeg_play: playing %lld bytes from %lld to %lld...\n", (end_idx->offset - idx->offset), idx->offset, end_idx->offset); fflush(stdout); printf("Start time: %s (gop %zd)\n", timestamp_to_string(NULL, start_timestamp), (idx - mpeg->index)); printf("End time: %s (gop %zd)\n", timestamp_to_string(NULL, end_timestamp), (end_idx - mpeg->index)); /* int rc; if ((rc = ivtv_api_dec_play(videofd, 0)) < 0) { fprintf(stderr, "ivtvplay: Warning: ivtv_api_dec_play() returned %d\n", rc); } */ while (idx < end_idx) { int chunk_size = ((idx+1)->offset - idx->offset); mpeg->offset = idx->offset; if (streamfd(videofd, mpeg->mpegfd, chunk_size) < 0) { fprintf(stderr, "mpeg_play: error %d (%s)\n", errno, strerror(errno)); goto done; } unsigned long long pts = 0, frame = 0; ioctl(videofd, VIDEO_GET_PTS, &pts); ioctl(videofd, VIDEO_GET_FRAME_COUNT, &frame); if (pts == 0 || frame == 0) { fprintf(stderr, "ioctl: VIDEO_GET_PTS/VIDEO_GET_FRAME_COUNT failed\n"); } else { static const double hz = 1596.027*1000*1000; double tframe = ((double)rdtsc()) / hz; char ptsstr[64]; pts_to_string(ptsstr, pts); printf("%10.6f: gop %-5zd bytes %-7d offset %-12lld pts %-12s frames %lld\n", tframe, (idx - mpeg->index), chunk_size, mpeg->offset, ptsstr, frame); } mpeg->offset += chunk_size; if (mpeg->offset == idx->offset) // end of file? break; idx++; } done: delete[] buf; /* if ((rc = ivtv_api_dec_pause(videofd, 0)) < 0) { fprintf(stderr, "ivtvplay: Warning: ivtv_api_dec_pause() returned %d\n", rc); } */ return 0; } //static int accum = 0; void sync_loop(long videofd) { printf("sync_loop started on videofd %ld\n", videofd); fflush(stdout); //++MTY This applies only to peptidyl: static const double hz = 1596.027*1000*1000; W64 tprev = rdtsc(); for (;;) { struct video_event ev; if (ioctl(videofd, VIDEO_GET_EVENT, &ev) < 0) { fprintf(stderr, "ioctl: VIDEO_GET_EVENT failed\n"); break; } else { W64 tframe = rdtsc(); double tdelta = (tframe - tprev) / hz; unsigned long long pts = 0, frame = 0; tprev = tframe; ioctl(videofd, VIDEO_GET_PTS, &pts); ioctl(videofd, VIDEO_GET_FRAME_COUNT, &frame); char ptsstr[64]; pts_to_string(ptsstr, pts); printf("%10.6f: pts %-20s, %lld frames\n", tdelta, ptsstr, frame); } } } void* sync_thread(void* arg) { sync_loop((long)arg); return NULL; } int main(int argc, char* argv[]) { if (argc < 5) { fprintf(stderr, "Syntax is:\n"); fprintf(stderr, "ivtvplay mpegfile /dev/videoX starthour:minute:second:frame endhour:minute:second:frame\n"); return -1; } struct mpeg_file mpeg; if (mpeg_open(&mpeg, argv[1]) < 0) return -2; int videofd; if ((videofd = open(argv[2], O_WRONLY | O_CREAT | O_APPEND | O_LARGEFILE, 0644)) < 0) { fprintf(stderr, "Failed to open video device %s: %s\n", argv[2], strerror(errno)); return -3; } int hour, minute, second, frame; if (sscanf(argv[3], "%d:%d:%d:%d", &hour, &minute, &second, &frame) != 4) { fprintf(stderr, "Bad start timestamp '%s'\n", argv[3]); return -4; } gop_header_t start_timestamp; start_timestamp.data = 0; start_timestamp.hour = hour; start_timestamp.minute = minute; start_timestamp.second = second; start_timestamp.frame = frame; if (sscanf(argv[4], "%d:%d:%d:%d", &hour, &minute, &second, &frame) != 4) { fprintf(stderr, "Bad end timestamp '%s'\n", argv[4]); return -4; } gop_header_t end_timestamp; end_timestamp.data = 0; end_timestamp.hour = hour; end_timestamp.minute = minute; end_timestamp.second = second; end_timestamp.frame = frame; pthread_t sync_thread_tid; pthread_create(&sync_thread_tid, NULL, sync_thread, (void*)(long)videofd); mpeg_play(&mpeg, videofd, start_timestamp, end_timestamp); return 0; }