#include "config.h" #include #include #include #include #include #include #include #include #include #include #ifdef __APPLE__ #include #else #include #include #endif #include #include #ifdef HAVE_INTTYPES_H #include #else #if LONG_MAX==2147483647L #define PRId64 "lld" #else #define PRId64 "ld" #endif #endif #if !(defined(__APPLE__) || defined(WIN32)) #define USE_AUDIO 1 #else #define USE_AUDIO 0 #endif #if USE_AUDIO #include #include #endif #include static int n_frames = 0; #ifdef DEBUG static long long total_audio_bytes = 0; static int target_audio_rate = 0; #endif static GLuint texture; static unsigned char *texture_bits = NULL; static int texture_width = 0; static int texture_height = 0; static float texture_wscale; static float texture_hscale; static int window_width = 0; static int window_height = 0; static OggPlay * player = NULL; static sem_t stop_sem; static int video_track = -1; static int audio_track = -1; static int kate_track = -1; #define DISPLAY_FRAMES 1 #define DISPLAY_FPS 0 #define PERIOD_SIZE 512 #ifdef DISPLAY_FPS static struct timeval tv0,tv1; #endif static void update_video_size(int width, int height) { int po2_width; int po2_height; if (width != window_width) { #if DISPLAY_FRAMES glutReshapeWindow(width, height); #endif window_width = width; window_height = height; #ifdef DEBUG printf("New window size is (%d, %d)\n", window_width, window_height); #endif } for (po2_width = 1; po2_width < width; po2_width <<= 1); for (po2_height = 1; po2_height < height; po2_height <<= 1); texture_wscale = (float) width / po2_width; texture_hscale = (float) height / po2_height; if (texture_bits == NULL) { texture_bits = calloc(1, po2_width * po2_height * 4); texture_width = po2_width; texture_height = po2_height; } else if (texture_width != po2_width || texture_height != po2_height) { free(texture_bits); texture_bits = calloc(1, po2_width * po2_height * 4); texture_width = po2_width; texture_height = po2_height; } } void handle_video_data (OggPlay * player, int track_num, OggPlayVideoData * video_data, int frame) { int y_width; int y_height; int uv_width; int uv_height; OggPlayYUVChannels yuv; OggPlayRGBChannels rgb; oggplay_get_video_y_size(player, track_num, &y_width, &y_height); update_video_size(y_width, y_height); oggplay_get_video_uv_size(player, track_num, &uv_width, &uv_height); //assert(uv_width == y_width / 2); //assert(uv_height == y_height / 2); /* * Convert the YUV data to RGB, using platform-specific optimisations * where possible. */ yuv.ptry = video_data->y; yuv.ptru = video_data->u; yuv.ptrv = video_data->v; yuv.uv_width = uv_width; yuv.uv_height = uv_height; yuv.y_width = y_width; yuv.y_height = y_height; rgb.ptro = texture_bits; rgb.rgb_width = texture_width; rgb.rgb_height = texture_height; oggplay_yuv2rgba(&yuv, &rgb); } void handle_overlay_data (OggPlay * player, int track_num, OggPlayOverlayData * overlay_data, int frame) { int w; int h; int x; int y; /* if we're getting RGBA (eg, the track was not set to be overlayed on top of video), but we do not actually have any video, then ignore alpha and blit the overlay as fully opaque since there's nothing behind it anyway */ if (overlay_data->rgb || video_track<0) { /* RGB video, no alpha */ const unsigned char *video = overlay_data->rgb ? overlay_data->rgb : overlay_data->rgba; update_video_size(overlay_data->width, overlay_data->height); for (y=0; y < overlay_data->height; ++y) { const unsigned char *src = video+y*overlay_data->stride; unsigned char *dest = texture_bits+y*texture_width*4; memcpy(dest, src, overlay_data->width*4); } } else { if (!texture_bits) { /* no video to overlay on, we draw the overlay only */ update_video_size(overlay_data->width, overlay_data->height); return; } /* overlay data onto rgb */ w = window_width; if (overlay_data->width < w) w = overlay_data->width; h = window_height; if (overlay_data->height < h) h = overlay_data->height; /* damn slow - full frame alpha blending in C - use liboil ? */ for (y=0; yrgba+y*overlay_data->stride; unsigned char *dest = texture_bits+y*texture_width*4; for (x=0; x 32767.0) ? 32767 : (short)scaled_value; } } } void init_audio() { int tmp; snd_fd = open("/dev/dsp", O_WRONLY, 0); #ifdef DEBUG target_audio_rate = (int)(rate * channels * sizeof(short) / 25); #endif tmp = AFMT_S16_LE; if (snd_fd >= 0) { ioctl(snd_fd, SNDCTL_DSP_SETFMT, &tmp); ioctl(snd_fd, SNDCTL_DSP_CHANNELS, &channels); ioctl(snd_fd, SNDCTL_DSP_SPEED, &rate); } } static short *buffer = NULL; static int buffer_length = 0; void handle_audio_data (OggPlay * player, int track, OggPlayAudioData * data, int size) { if (snd_fd == -1) { init_audio(); } if (buffer_length < size) { /* could overflow if number of channels goes up midway (eg, chained stream) but at least it doesn't access bad memory for mono streams now */ short *new_buffer = realloc(buffer, size * sizeof (short) * channels); if (!new_buffer) return; buffer = new_buffer; buffer_length = size; } float_to_short_array((float *)data, buffer, size * channels); if (snd_fd >= 0) { write(snd_fd, buffer, size * channels * sizeof(short)); } } #endif static int window; static long ld_time; void display_frame(void) { int i; OggPlayDataHeader ** headers; OggPlayVideoData * video_data; OggPlayOverlayData * overlay_data; #if USE_AUDIO OggPlayAudioData * audio_data; count_info tsc; #endif int j; int required; OggPlayDataType type; int num_tracks; OggPlayCallbackInfo ** track_info; static unsigned int target = 0; #ifdef DEBUG int bytes_per_frame = 0; #endif struct timespec ts; ts.tv_sec = 0; num_tracks = oggplay_get_num_tracks (player); //nice(5); //while (1) { track_info = oggplay_buffer_retrieve_next(player); if (track_info == NULL) { ts.tv_nsec = 40000000; nanosleep(&ts, NULL); return; } #if USE_AUDIO if (rate > 0) { long long offset; if (snd_fd >= 0) { ioctl(snd_fd, SNDCTL_DSP_GETOPTR, &tsc); long long bytes = tsc.bytes; offset = (target - (bytes * 10000 / rate / 4)) * 100; } else { offset = fps_denom*10000/fps_num * 100; } #else { long long offset = fps_denom*10000/fps_num * 100; #endif target += fps_denom*10000/fps_num; #if DISPLAY_FRAMES if (offset > 0) { ts.tv_nsec = offset * 1000; nanosleep(&ts, NULL); } #endif } for (i = 0; i < num_tracks; i++) { type = oggplay_callback_info_get_type(track_info[i]); headers = oggplay_callback_info_get_headers(track_info[i]); switch (type) { case OGGPLAY_INACTIVE: break; case OGGPLAY_YUV_VIDEO: /* * there should only be one record */ required = oggplay_callback_info_get_required(track_info[i]); video_data = oggplay_callback_info_get_video_data(headers[0]); ld_time = oggplay_callback_info_get_presentation_time(headers[0]); handle_video_data(player, i, video_data, n_frames); /* printf("video fst %ld lst %ld\n", oggplay_callback_info_get_presentation_time(headers[0]), oggplay_callback_info_get_presentation_time(headers[required - 1])); */ break; case OGGPLAY_RGBA_VIDEO: /* * there should only be one record */ required = oggplay_callback_info_get_required(track_info[i]); if (required>0) { overlay_data = oggplay_callback_info_get_overlay_data(headers[0]); ld_time = oggplay_callback_info_get_presentation_time(headers[0]); handle_overlay_data(player, i, overlay_data, n_frames); } break; case OGGPLAY_FLOATS_AUDIO: #if USE_AUDIO required = oggplay_callback_info_get_required(track_info[i]); /* printf("audio fst %ld lst %ld\n", oggplay_callback_info_get_presentation_time(headers[0]), oggplay_callback_info_get_presentation_time(headers[required - 1])); */ int total_size = 0; for (j = 0; j < required; j++) { int size; size = oggplay_callback_info_get_record_size(headers[j]); audio_data = oggplay_callback_info_get_audio_data(headers[j]); #ifdef DEBUG bytes_per_frame += size * 4; // size * channels #endif handle_audio_data(player, i, audio_data, size); total_size += size; } #endif break; case OGGPLAY_CMML: if (oggplay_callback_info_get_required(track_info[i]) > 0) printf("%s\n", oggplay_callback_info_get_text_data(headers[0])); break; case OGGPLAY_KATE: required = oggplay_callback_info_get_required(track_info[i]); for (j = 0; j < required; j++) { printf("[%d] [duration %"PRId64"] %s\n", i, oggplay_callback_info_get_record_size(headers[j]), oggplay_callback_info_get_text_data(headers[j])); } break; default: break; } } n_frames++; #ifdef DEBUG total_audio_bytes += bytes_per_frame; printf("Processing frame: %d | audio B/s: %d | average audio B/s %f | targetaudio rate %d B/s\n", n_frames, bytes_per_frame, (float)(total_audio_bytes/n_frames), target_audio_rate); bytes_per_frame = 0; #endif #ifdef DISPLAY_FPS gettimeofday(&tv1,NULL); printf("%.2f fps \r",1000000.0/(tv1.tv_usec-tv0.tv_usec+1000000*(tv1.tv_sec-tv0.tv_sec))); fflush(stdout); tv0=tv1; #endif #if DISPLAY_FRAMES #if USE_AUDIO if (snd_fd >= 0) { ioctl(snd_fd, SNDCTL_DSP_GETOPTR, &tsc); } #endif glutPostRedisplay(); //show_window(); #endif oggplay_buffer_release (player, track_info); //} } typedef enum { SEEK_FORWARD, SEEK_BACKWARD } MessageEnum; MessageEnum msg; static sem_t msg_sem; static void cleanup() { #if DISPLAY_FRAMES glutDestroyWindow(window); #endif if (texture_bits != NULL) { free(texture_bits); texture_bits = NULL; } glDeleteTextures(1,&texture); oggplay_close(player); } void key_pressed(unsigned char k, int a, int b) { if (k == 'q') { cleanup(); exit(0); } else if (k == 'l') { msg = SEEK_FORWARD; sem_post(&msg_sem); } else if (k == 'k') { msg = SEEK_BACKWARD; sem_post(&msg_sem); } } #if DISPLAY_FRAMES void show_window(void) { if (texture_bits != NULL) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_width, texture_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, texture_bits); } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glBegin(GL_QUADS); glTexCoord2f(0.0, texture_hscale); glVertex2f(-1,-1); glTexCoord2f(texture_wscale, texture_hscale); glVertex2f(1,-1); glTexCoord2f(texture_wscale, 0.0); glVertex2f(1,1); glTexCoord2f(0.0, 0.0); glVertex2f(-1,1); glEnd(); glutSwapBuffers(); } #endif static int saved_avail = 0; void *drive_decoding(void *arg) { while (1) { OggPlayErrorCode r; int avail; if (sem_trywait(&msg_sem) == 0) { if (msg == SEEK_FORWARD) { if (oggplay_seek(player, ld_time + 5000) == E_OGGPLAY_CANT_SEEK) { printf("can't seek forwards!\n"); } } else if (msg == SEEK_BACKWARD) { if (oggplay_seek(player, ld_time - 5000) == E_OGGPLAY_CANT_SEEK) { printf("cant seek backwards!\n"); } } msg = 0; } r = E_OGGPLAY_TIMEOUT; while (r == E_OGGPLAY_TIMEOUT) { r = oggplay_step_decoding(player); } avail = oggplay_get_available(player); if (avail != saved_avail) { saved_avail = avail; //printf("available: %d\n", avail); } if (r != E_OGGPLAY_CONTINUE && r != E_OGGPLAY_USER_INTERRUPT) { printf("hmm, totally bogus, dude. r is %d\n", r); // wait for display thread to finish sem_wait(&stop_sem); #if DISPLAY_FRAMES glutDestroyWindow(window); #endif pthread_exit(NULL); } } } int main (int argc, char * argv[]) { OggPlayReader * reader; int i; pthread_t thread; if (argc < 2) { printf ("please provide a filename\n"); exit (1); } if (strncmp(argv[1], "http://", 7) == 0) { reader = oggplay_tcp_reader_new(argv[1], NULL, 0); } else { reader = oggplay_file_reader_new(argv[1]); } player = oggplay_open_with_reader(reader); if (player == NULL) { printf ("could not initialise oggplay with this file\n"); exit (1); } printf ("there are %d tracks\n", oggplay_get_num_tracks (player)); video_track = -1; audio_track = -1; kate_track = -1; for (i = 0; i < oggplay_get_num_tracks (player); i++) { int activate_track = 1; printf("Track %d is of type %s\n", i, oggplay_get_track_typename (player, i)); if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_THEORA) { int ret; oggplay_set_callback_num_frames (player, i, 1); video_track = i; ret = oggplay_get_video_fps(player, i , &fps_denom, &fps_num); } else if ( oggplay_get_track_type (player, i) == OGGZ_CONTENT_VORBIS || oggplay_get_track_type (player, i) == OGGZ_CONTENT_SPEEX ) { int ret; audio_track = i; oggplay_set_offset(player, i, 500L); ret = oggplay_get_audio_samplerate(player, i , &rate); ret = oggplay_get_audio_channels(player, i, &channels); printf("samplerate: %d channels: %d\n", rate, channels); } else if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_KATE) { const char *category = "", *language = ""; int ret = oggplay_get_kate_category(player, i, &category); ret = oggplay_get_kate_language(player, i, &language); printf("category %s, language %s\n", category, language); if (kate_track < 0) { kate_track = i; if (video_track != -1) { /* if we have video, request overlaying onto the video */ oggplay_convert_video_to_rgb(player, video_track, 1); oggplay_overlay_kate_track_on_video(player, i, video_track); } } else { /* do not activate more than one Kate track, they're probably going to be subtitles in various languages, so will end up overlapping each other - comment this line out if they're supposed to be displayed on top of each other (eg subtitles+logo, etc) */ activate_track = 0; } } if (activate_track) { if (oggplay_set_track_active(player, i) < 0) { printf("\tNote: Could not set this track active!\n"); } } } if (video_track == -1) { if (audio_track >= 0) { oggplay_set_callback_num_frames(player, audio_track, 2048); } else if (kate_track >= 0) { /* there was no video nor audio, so play the Kate track alone */ oggplay_set_callback_period (player, kate_track, 40); /* in milliseconds, 25 fps */ } else { /* no video... no audio... and no kate... what we gonna do ??? */ } } #if DISPLAY_FRAMES glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowPosition(100, 100); glutInitWindowSize(400, 400); #endif window_width = 400; window_height = 400; #if DISPLAY_FRAMES window = glutCreateWindow("glut player"); #endif oggplay_use_buffer(player, 20); sem_init(&stop_sem, 1, 1); sem_wait(&stop_sem); sem_init(&msg_sem, 1, 1); sem_wait(&msg_sem); pthread_create(&thread, NULL, drive_decoding, NULL); #if DISPLAY_FRAMES glutIdleFunc(&display_frame); glutDisplayFunc(&show_window); glutKeyboardFunc(&key_pressed); //glutDisplayFunc(&empty); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBindTexture(GL_TEXTURE_2D, texture); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glGenTextures(1, &texture); glutMainLoop(); #else for (i = 0; i < 100; i++) { display_frame(); } while (1) { key_pressed('l', 0, 0); for (i = 0; i < 10; i++) { display_frame(); } key_pressed('l', 0, 0); for (i = 0; i < 10; i++) { display_frame(); } key_pressed('l', 0, 0); for (i = 0; i < 100; i++) { display_frame(); } key_pressed('k', 0, 0); for (i = 0; i < 100; i++) { display_frame(); } } key_pressed('q', 0, 0); #endif printf("there were %d frames\n", n_frames); return 0; }