#include #include #include #define N_OGG_BUFS 20 static OggPlayReader *reader = NULL; static OggPlay *player = NULL; static IBNibRef nibRef = NULL; static AGLContext aglContext = NULL; static MPSemaphoreID sem = NULL; static bool run_display = TRUE; static pthread_t decode_thread; static pthread_t display_thread; static WindowRef window; static int n_frames = 0; static GLuint texture = 0; static unsigned char *texture_bits = NULL; static int texture_width; static int texture_height; static float texture_wscale; static float texture_hscale; //static Rect window_bounds; // All GL calls must be made in the same thread, so any initialisations that // have a corresponding shutdown must be performed by the display thread // (rather than at the end of the main function, where most other shutdowns // are being handled). bool init_gl() { CGrafPtr port; AGLPixelFormat aglPixelFormat; GLint pixelAttr[] = { AGL_RGBA, AGL_DOUBLEBUFFER, AGL_PIXEL_SIZE, 32, AGL_ACCELERATED, AGL_NONE }; // Initialise the AGL drawing context and attach it to the window's graphics port. port = GetWindowPort(window); if (port == NULL) { printf("GetWindowPort failed\n"); return FALSE; } aglPixelFormat = aglChoosePixelFormat(NULL, 0, pixelAttr); if (aglPixelFormat == NULL) { printf("aglChoosePixelFormat failed\n"); return FALSE; } aglContext = aglCreateContext(aglPixelFormat, NULL); aglDestroyPixelFormat(aglPixelFormat); if (aglContext == NULL) { printf("aglCreateContext failed\n"); return FALSE; } if (!aglSetDrawable(aglContext, port)) { printf("aglSetDrawable failed\n"); return FALSE; } if (!aglSetCurrentContext(aglContext)) { printf("aglSetCurrentContext failed\n"); return FALSE; } // Prepare GL for drawing a 2d texture (image). glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glGenTextures(1, &texture); 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); if (glGetError() != GL_NO_ERROR) { printf("An OpenGL function call failed\n"); return FALSE; } return TRUE; } static void update_gl_texture() { 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(); aglSwapBuffers(aglContext); } static void shutdown_gl() { if (texture != 0) glDeleteTextures(1, &texture); if (aglContext != NULL) aglDestroyContext(aglContext); } static void handle_video_data(int track_num, OggPlayVideoData *video_data) { int y_width; int y_height; int uv_width; int uv_height; int po2_width; int po2_height; OggPlayYUVChannels yuv; OggPlayRGBChannels rgb; oggplay_get_video_y_size(player, track_num, &y_width, &y_height); /* if (y_width != window_bounds.right - window_bounds.left) { window_bounds.right = window_bounds.left + y_width; window_bounds.bottom = window_bounds.top + y_height; SetWindowBounds(window, kWindowContentRgn, &window_bounds); } */ oggplay_get_video_uv_size(player, track_num, &uv_width, &uv_height); assert(uv_width == y_width / 2); assert(uv_height == y_height / 2); for (po2_width = 1; po2_width < y_width; po2_width <<= 1); for (po2_height = 1; po2_height < y_height; po2_height <<= 1); texture_wscale = (float)y_width / po2_width; texture_hscale = (float)y_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; } 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); } static void * display_frame(void *arg) { #pragma unused(arg) OggPlayDataHeader **headers; OggPlayVideoData *video_data; int required; OggPlayDataType type; int num_tracks; OggPlayCallbackInfo **track_info; int i; if (!init_gl()) { shutdown_gl(); return NULL; } num_tracks = oggplay_get_num_tracks(player); // run_display will be set if the window is closed, so we can // terminate this thread gracefully and perform appropriate shutdown. while (run_display) { track_info = oggplay_buffer_retrieve_next(player); if (track_info == NULL) { struct timespec ts; ts.tv_nsec = 40000000; nanosleep(&ts, NULL); continue; } 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: required = oggplay_callback_info_get_required(track_info[i]); video_data = oggplay_callback_info_get_video_data(headers[0]); handle_video_data(i, video_data); break; case OGGPLAY_FLOATS_AUDIO: 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] %s\n", j, oggplay_callback_info_get_text_data(headers[j])); break; default: break; } } update_gl_texture(); n_frames++; //printf("%d\n", n_frames); oggplay_buffer_release(player, track_info); MPSignalSemaphore(sem); } shutdown_gl(); return NULL; } void * drive_decoding(void *arg) { #pragma unused(arg) while (1) { OggPlayErrorCode result; MPWaitOnSemaphore(sem, kDurationForever); result = oggplay_step_decoding(player); if (result != E_OGGPLAY_CONTINUE && result != E_OGGPLAY_USER_INTERRUPT) { printf("*** oggplay_step_decoding() failed in drive_decoding() ***\n"); return NULL; } } } // The window and app event handlers have been left as skeletons here in case // we ever want to add multiple windows or other application behaviours. static OSStatus WindowEventHandler(EventHandlerCallRef caller, EventRef event, void *arg) { #pragma unused(caller, arg) OSStatus result = eventNotHandledErr; UInt32 class = GetEventClass(event); UInt32 kind = GetEventKind(event); switch (class) { case kEventClassWindow: { WindowRef winRef; GetEventParameter(event, kEventParamDirectObject, typeWindowRef, NULL, sizeof(winRef), NULL, &winRef); switch (kind) { case kEventWindowBoundsChanged: //GetWindowBounds(window, kWindowContentRgn, &window_bounds); break; case kEventWindowClosed: // Signal the display thread to shut down. run_display = FALSE; break; } //switch (kind) break; } // case kEventClassWindow } //switch (class) return result; } static OSStatus AppEventHandler(EventHandlerCallRef caller, EventRef event, void *arg) { #pragma unused(caller, arg) OSStatus result = eventNotHandledErr; UInt32 class = GetEventClass(event); UInt32 kind = GetEventKind(event); switch (class) { case kEventClassApplication: { switch (kind) { case kEventAppQuit: // Signal the display thread to shut down. run_display = FALSE; break; } // switch (kind) } // case kEventClassApplication case kEventClassCommand: { HICommandExtended cmd; GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd); switch (kind) { case kEventCommandProcess: { switch (cmd.commandID) { case kHICommandOpen: //-- file open handling would go here -- break; } //switch (cmd.commandID) break; } // case kEventCommandProcess } //switch (kind) break; } // case kEventClassCommand } //switch (class) return result; } static bool InitApp() { EventTypeSpec appEvents[] = { { kEventClassApplication, kEventAppQuit }, { kEventClassCommand, kEventCommandProcess } }; EventTypeSpec winEvents[] = { { kEventClassWindow, kEventWindowBoundsChanged }, { kEventClassWindow, kEventWindowClosed } }; // Create a Nib reference, passing the name of the nib file (without the .nib extension). // CreateNibReference only searches in the application bundle. if (CreateNibReference(CFSTR("gplayer"), &nibRef) != noErr) { printf("CreateNibReference failed\n"); return FALSE; } // Once the nib reference is created, set the menu bar. if (SetMenuBarFromNib(nibRef, CFSTR("MenuBar")) != noErr) { printf("SetMenuBarFromNib failed\n"); return FALSE; } // Create a window, using the settings from the nib file. The default close event // handler apparently releases the resources for this, so we don't need any calls // to ReleaseWindow()... I hope. if (CreateWindowFromNib(nibRef, CFSTR("ViewWindow"), &window) != noErr) { printf("CreateWindowFromNib failed\n"); return FALSE; } //GetWindowBounds(window, kWindowContentRgn, &window_bounds); // Install the window and application event handlers. if (InstallApplicationEventHandler(NewEventHandlerUPP(AppEventHandler), GetEventTypeCount(appEvents), appEvents, NULL, NULL) != noErr) { printf("InstallApplicationEventHandler failed\n"); return FALSE; } if (InstallWindowEventHandler(window, NewEventHandlerUPP(WindowEventHandler), GetEventTypeCount(winEvents), winEvents, NULL, NULL) != noErr) { printf("InstallWindowEventHandler failed\n"); return FALSE; } // The window is hidden on creation, so show it. ShowWindow(window); return TRUE; } int main(int argc, char *argv[]) { char *media; int i; // Open the requested AXV file (use a default if not provided). if (argc > 1) { media = argv[1]; } else { media = "http://media.annodex.net/cmmlwiki/SFD2005-Trailer.axv"; printf("No source provided; using default\n"); } if (strncmp(media, "http://", 7) == 0) { reader = oggplay_tcp_reader_new(media); } else { reader = oggplay_file_reader_new(media); } if (reader == NULL) { printf("oggplay_file_reader_new failed\n"); return 1; } player = oggplay_open_with_reader(reader); if (player == NULL) { printf("Could not initialise oggplay with this file\n"); goto done; } printf("There are %d tracks\n", oggplay_get_num_tracks(player)); for (i = 0; i < oggplay_get_num_tracks(player); i++) { printf("Track %d is of type %s\n", i, oggplay_get_track_typename(player, i)); switch (oggplay_get_track_type(player, i)) { case OGGZ_CONTENT_THEORA: oggplay_set_callback_num_frames(player, i, 1); break; case OGGZ_CONTENT_VORBIS: oggplay_set_offset(player, i, 250L); break; } if (oggplay_set_track_active(player, i) < 0) printf("\tNote: could not set this track active!\n"); } oggplay_use_buffer(player, N_OGG_BUFS); // Create the display window. if (!InitApp()) goto done; // Set up the semaphore used to coordinate decoding and displaying. if (MPCreateSemaphore(N_OGG_BUFS, N_OGG_BUFS, &sem) != noErr) { printf("Failed to create decoding semaphore\n"); goto done; } // Kick off one thread to continually decode the AXV file, and another // to display when frames become available. if (pthread_create(&decode_thread, NULL, drive_decoding, NULL) != 0) { printf("pthread_create failed\n"); goto done; } if (pthread_create(&display_thread, NULL, display_frame, NULL) != 0) { printf("pthread_create failed\n"); goto done; } RunApplicationEventLoop(); done: if (sem != NULL) MPDeleteSemaphore(sem); if (nibRef != NULL) DisposeNibReference(nibRef); //-- should destroy player here (currently there is no function to do this) -- if (reader != NULL) reader->destroy(reader); return 0; }