#include "config_win32.h" #include #include #include #include #include #define OPENGL 1 #if OPENGL #include // requires OpenGL for Win32 #endif /* !OPENGL */ #define USE_AUDIO 0 #if USE_AUDIO #include #include #endif #include static int n_frames = 0; static DWORD position = 0; static UINT target = 0; static DWORD rate = 0; static UINT channels = 2; static BOOL buffering = TRUE; static BOOL audio_opened = FALSE; static UINT total_bytes = 0; #if OPENGL static GLuint texture; #endif #define OGGPLAY_BUFFER_SIZE 20 #define BLOCK_SIZE 2560 #define BLOCK_COUNT 4 static unsigned char *texture_bits = NULL; static int texture_width; static int texture_height; static float texture_wscale; static float texture_hscale; static int window_width; static int window_height; static int window_style; static OggPlay* player = NULL; static CRITICAL_SECTION waveCriticalSection; static HWAVEOUT hWaveOut; static WAVEHDR* waveBlocks = NULL; static volatile int waveFreeBlockCount; static int waveCurrentBlock; static HANDLE audio_synch; typedef struct { HANDLE decode_thread; HANDLE display_thread; } Player_Info; // oggplay buffer underrun semaphore static HANDLE sem; static HWAVEOUT audio_dev; static int video_track; static int audio_track; #define APPLICATIONNAME TEXT("OggPlay Media Player\0") #define CLASSNAME TEXT("OggPlayMediaPlayer\0") #if OPENGL static int window; #else static HWND window; #endif /*OPENGL*/ #define DISPLAY_FRAMES 1 void handle_video_data (OggPlay * player, int track_num, OggPlayVideoData * video_data, int frame) { int i; int y_width; int y_height; int uv_width; int uv_height; int po2_width; int po2_height; int style; OggPlayYUVChannels yuv; OggPlayRGBChannels rgb; RECT r; #if 0 unsigned char* ptry; unsigned char* ptru; unsigned char* ptrv; unsigned char* ptro; unsigned char* ptro2; #endif oggplay_get_video_y_size(player, track_num, &y_width, &y_height); if (y_width != window_width) { #if DISPLAY_FRAMES #if OPENGL glutReshapeWindow(y_width, y_height); #else style = window_style & ~ WS_OVERLAPPED; GetClientRect(window, &r); r.right = r.left + y_width + 5; r.bottom = r.top + y_height + 5; // allow for the border, title size, etc. AdjustWindowRect(&r, style, FALSE); SetWindowPos(window, HWND_TOP, 0, 0, r.right - r.left, r.bottom - r.top, SWP_ASYNCWINDOWPOS | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_SHOWWINDOW); UpdateWindow(window); #endif /* !OPENGL */ #endif; window_width = y_width; window_height = 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); 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 = (unsigned char*)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 = (unsigned char*)calloc(1, po2_width * po2_height * 4); texture_width = po2_width; texture_height = po2_height; } /* * R = Y + 1.140V * G = Y - 0.395U - 0.581V * B = Y + 2.032U */ 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; #if OPENGL oggplay_yuv2rgba(&yuv, &rgb); #else oggplay_yuv2bgra(&yuv, &rgb); #endif } #if USE_AUDIO void float_to_short_array(const float* in, short* out, int len) { int i = 0; float scaled_value = 0; for(i = 0; i < len; i++) { scaled_value = floorf(0.5 + 32768 * in[i]); if (in[i] < 0) { out[i] = (scaled_value < -32768.0) ? -32768 : (short)scaled_value; } else { out[i] = (scaled_value > 32767.0) ? 32767 : (short)scaled_value; } } } WAVEHDR* allocateBlocks(int size, int count) { unsigned char* buffer; int i; WAVEHDR* blocks; DWORD totalBufferSize = (size + sizeof(WAVEHDR)) * count; /* * allocate memory for the entire set in one go */ if((buffer = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, totalBufferSize )) == NULL) { printf("Memory allocation error\n"); ExitProcess(1); } /* * and set up the pointers to each bit */ blocks = (WAVEHDR*)buffer; buffer += sizeof(WAVEHDR) * count; for(i = 0; i < count; i++) { blocks[i].dwBufferLength = size; blocks[i].lpData = buffer; buffer += size; } return blocks; } void freeBlocks(WAVEHDR* blockArray) { /* * and this is why allocateBlocks works the way it does */ HeapFree(GetProcessHeap(), 0, blockArray); } static void CALLBACK waveOutProc( HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 ) { /* * pointer to free block counter */ int* freeBlockCounter = (int*)dwInstance; /* * ignore calls that occur due to openining and closing the * device. */ if(uMsg != WOM_DONE) return; EnterCriticalSection(&waveCriticalSection); (*freeBlockCounter)++; /*if ((*freeBlockCounter) == 1) SetEvent(audio_synch);*/ LeaveCriticalSection(&waveCriticalSection); } void openAudio(OggPlay * player, int track) { WAVEFORMATEX wfx; UINT fps = 25; // get from the stream information UINT supported = FALSE; waveBlocks = allocateBlocks(BLOCK_SIZE, BLOCK_COUNT); waveFreeBlockCount = BLOCK_COUNT; waveCurrentBlock = 0; wfx.nSamplesPerSec = (DWORD)rate; /* sample rate */ wfx.wBitsPerSample = 16; /* sample size */ wfx.nChannels = channels; /* channels */ wfx.cbSize = 0; /* size of _extra_ info */ wfx.wFormatTag = WAVE_FORMAT_PCM; wfx.nBlockAlign = (wfx.wBitsPerSample * wfx.nChannels) >> 3; wfx.nAvgBytesPerSec = wfx.nBlockAlign * wfx.nSamplesPerSec; // target sample size in bytes for each frame target = wfx.nAvgBytesPerSec / fps; supported = waveOutOpen(NULL, WAVE_MAPPER, &wfx, (DWORD_PTR)0, (DWORD_PTR)0, WAVE_FORMAT_QUERY); if (supported == MMSYSERR_NOERROR) { // audio device sucessfully opened waveOutOpen((LPHWAVEOUT)&hWaveOut, WAVE_MAPPER, &wfx, (DWORD_PTR)waveOutProc, (DWORD_PTR)&waveFreeBlockCount, CALLBACK_FUNCTION); //printf("Audio device sucessfully opened\n"); } else if (supported == WAVERR_BADFORMAT) { printf("Requested format not supported...\n"); ExitProcess(1); } else { printf("Error opening default audio device. Exiting...\n"); ExitProcess(1); } return; } void writeAudio(LPSTR data, int samples) { WAVEHDR* current; int bytes; int remain; current = &waveBlocks[waveCurrentBlock]; while(samples > 0) { /* * first make sure the header we're going to use is unprepared */ if(current->dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(hWaveOut, current, sizeof(WAVEHDR)); bytes = sizeof(short) * samples; total_bytes += bytes; if(bytes < (int)(BLOCK_SIZE - current->dwUser)) { float_to_short_array((float*)data, (short*)(current->lpData + current->dwUser), samples); //memcpy(current->lpData + current->dwUser, data, size); current->dwUser += bytes; break; } // bytes is even as BLOCK_SIZE and dwUser are even too bytes = BLOCK_SIZE - current->dwUser; remain = bytes / sizeof(short); // samples left in the buffer float_to_short_array((float*)data, (short*)(current->lpData + current->dwUser), remain); //memcpy(current->lpData + current->dwUser, data, remain); samples -= remain; data += 2 * bytes; current->dwBufferLength = BLOCK_SIZE; waveOutPrepareHeader(hWaveOut, current, sizeof(WAVEHDR)); waveOutWrite(hWaveOut, current, sizeof(WAVEHDR)); if (buffering == TRUE) { printf("Total bytes %d\n", total_bytes); } buffering = FALSE; EnterCriticalSection(&waveCriticalSection); waveFreeBlockCount--; LeaveCriticalSection(&waveCriticalSection); /* * wait for a block to become free */ while (!waveFreeBlockCount) { //printf("All audio buffer blocks empty\n"); //WaitForSingleObject(audio_synch, INFINITE); Sleep(10); } /* * point to the next block */ waveCurrentBlock++; waveCurrentBlock %= BLOCK_COUNT; current = &waveBlocks[waveCurrentBlock]; current->dwUser = 0; } } void closeAudio() { int i; /* * wait for all blocks to complete */ while(waveFreeBlockCount < BLOCK_COUNT) Sleep(10); /* * unprepare any blocks that are still prepared */ for(i = 0; i < waveFreeBlockCount; i++) if(waveBlocks[i].dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(hWaveOut, &waveBlocks[i], sizeof(WAVEHDR)); freeBlocks(waveBlocks); waveOutClose(hWaveOut); } void handle_audio_data (OggPlay * player, int track, OggPlayAudioData * data, int size) { if (audio_opened == FALSE) { openAudio(player, track); audio_opened = TRUE; } writeAudio((LPSTR)data, size); } #endif #if !OPENGL DWORD WINAPI display_frame(void *arg) { #else void display_frame(void) { #endif int i; int j; OggPlayDataHeader ** headers; OggPlayVideoData * video_data; #if USE_AUDIO OggPlayAudioData * audio_data; #endif int size; int required; OggPlayDataType type; int num_tracks; OggPlayCallbackInfo ** track_info; DWORD offset = 0; DWORD delay; MMTIME mm; delay = 40; // 40ms timeslots @ 25 fps num_tracks = oggplay_get_num_tracks (player); //nice(5); #if !OPENGL while (1) { #endif track_info = oggplay_buffer_retrieve_next(player); if (track_info == NULL) { #if DISPLAY_FRAMES Sleep(delay); #if OPENGL return; #else continue; #endif } #endif // make sure the playback rate is correct if (buffering == FALSE) { mm.wType = TIME_BYTES; waveOutGetPosition(hWaveOut, &mm, sizeof(MMTIME)); if (target > (DWORD)mm.u.cb - position) { offset = (target - (DWORD)mm.u.cb + position) * delay / target; Sleep(offset); } position = (DWORD)mm.u.cb; } 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]); if (required == 0) { oggplay_buffer_release(player, track_info); goto next_frame; } video_data = oggplay_callback_info_get_video_data(headers[0]); //printf("video presentation time: %llx\n", // oggplay_callback_info_get_presentation_time(headers[0])); handle_video_data(player, i, video_data, n_frames); break; case OGGPLAY_FLOATS_AUDIO: #if USE_AUDIO required = oggplay_callback_info_get_required(track_info[i]); // fill in fist and second buffer for (j = 0; j < required; j++) { size = oggplay_callback_info_get_record_size(headers[j]); audio_data = oggplay_callback_info_get_audio_data(headers[j]); handle_audio_data(player, i, audio_data, channels * size); } //printf("audio presentation time: %llx\n", // oggplay_callback_info_get_presentation_time(headers[j])); #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] %s\n", j, oggplay_callback_info_get_text_data(headers[j])); break; default: break; } } n_frames++; #if DISPLAY_FRAMES #if USE_AUDIO #ifdef WIN32 //QueryPerformanceCounter(&last_tick_count); #endif #endif #if OPENGL glutPostRedisplay(); #else // send WM_PAINT to the player window InvalidateRect(window, NULL, TRUE); UpdateWindow(window); #endif #endif /* DISPLAY_FRAMES */ oggplay_buffer_release(player, track_info); next_frame: ReleaseSemaphore(sem, 1, NULL); #if !OPENGL } #endif } #if DISPLAY_FRAMES void show_window(void) { #if OPENGL 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 } #endif DWORD WINAPI drive_decoding(void *arg) { while (1) { OggPlayErrorCode r; WaitForSingleObject(sem, INFINITE); r = oggplay_step_decoding(player); if (r != E_OGGPLAY_CONTINUE && r != E_OGGPLAY_USER_INTERRUPT) { #if DISPLAY_FRAMES #if OPENGL glutDestroyWindow(window); #endif #endif // stop all treads and close the window SendMessage(window, WM_CLOSE, (WPARAM)0, (LPARAM)0); Sleep(10); } } } #if !OPENGL void CreateDDBitmap(HDC hdc, HBITMAP* hBitmap) { BITMAPINFOHEADER bih; BITMAPINFO bmi; // fill in BITMAPINFOHEADER ZeroMemory(&bih, sizeof(BITMAPINFOHEADER)); bih.biClrImportant = 0; bih.biClrUsed = 0; bih.biXPelsPerMeter = 0; bih.biYPelsPerMeter = 0; bih.biSize = 40; bih.biWidth = texture_width; bih.biHeight = -texture_height; bih.biPlanes = 1; bih.biBitCount = 32; bih.biCompression = BI_RGB; bih.biSizeImage = ((bih.biWidth * 32 + 31) & ~31) /8 * (-bih.biHeight); //(((bih.biWidth * bih.biBitCount / 8) + 3) & ~3) * bih.biHeight; // fill in BITMAPINFO ZeroMemory(&bmi, sizeof(BITMAPINFO)); bmi.bmiHeader = bih; // create bitmap from raw rgb bits SetDIBits(hdc, (*hBitmap), 0, texture_height, texture_bits, &bmi, DIB_RGB_COLORS); /*SetDIBitsToDevice(hdc, 0, 0, texture_width, texture_height, 0, 0, 0, texture_height, texture_bits, &bmi, DIB_RGB_COLORS);*/ return; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; RECT r; HDC hdc, hdcMem; HBITMAP hbmOld, hbm; BITMAP bm; Player_Info* info = NULL; switch(message) { case WM_PAINT : { hdc = BeginPaint(window, &ps); GetClientRect(window, &r); /* FIXME: very performance costly operations, use global context? */ hdcMem = CreateCompatibleDC(hdc); hbm = CreateCompatibleBitmap(hdc, texture_width, texture_height); CreateDDBitmap(hdc, &hbm); hbmOld = SelectObject(hdcMem, hbm); if ((r.right - r.left != window_width) || (r.bottom - r.top != window_height)) { StretchBlt(hdc, r.left, r.top, r.right - r.left, r.bottom - r.top, hdcMem, r.left, r.top, r.left + window_width, r.top + window_height, SRCCOPY); } else { BitBlt(hdc, 0, 0, window_width, window_height, hdcMem, 0, 0, SRCCOPY); } //restore the initial state SelectObject(hdcMem, hbmOld); DeleteObject(hbm); DeleteDC(hdcMem); EndPaint(window, &ps); } break; case WM_CLOSE: { info = (Player_Info*)GetWindowLong(window, GWL_USERDATA); TerminateThread(info->decode_thread, (DWORD)0); TerminateThread(info->display_thread, (DWORD)0); CloseHandle(info->decode_thread); CloseHandle(info->display_thread); ExitProcess(0); /*SuspendThread(info->decode_thread); SuspendThread(info->display_thread);*/ } break; case WM_DESTROY: { PostQuitMessage(0); } break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { #else int main (int argc, char * argv[]) { #endif OggPlayReader * reader; int i; MSG msg = {0}; WNDCLASSEX wc; Player_Info info; DWORD dec_id; DWORD disp_id; LONG offset = 0L; BOOL fGotMessage; #if OPENGL 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); #else reader = oggplay_file_reader_new(lpCmdLine); //reader = oggplay_tcp_reader_new(); //player = oggplay_open_with_reader(reader, "http://media.annodex.net/cmmlwiki/SFD2005-Trailer.axv"); //player = oggplay_open_with_reader(reader, "E:\\_marcin\\_devel\\ogg_play\\index.anx"); player = oggplay_open_with_reader(reader); #endif if (player == NULL) { printf ("could not initialise oggplay with this file\n"); return 1; } 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)); if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_THEORA) { oggplay_set_callback_num_frames (player, i, 1); video_track = i; } else if (oggplay_get_track_type (player, i) == OGGZ_CONTENT_VORBIS) { audio_track = i; channels = 2;//oggplay_get_audio_channels(player, audio_track); if (!channels) printf("Problems reading channel information\n"); rate = 16000;//oggplay_get_audio_samplerate(player, audio_track); if (!rate) printf("Problems retreiving sample rate information\n"); // calculate audio offset in [ms] offset = (LONG)(1000 * BLOCK_SIZE * BLOCK_COUNT / channels / sizeof(short) / rate); printf("Calculated offset: %d\n", offset); oggplay_set_offset(player, i, offset); } if (oggplay_set_track_active(player, i) < 0) { printf("\tNote: Could not set this track active!\n"); } } #if DISPLAY_FRAMES #if OPENGL glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowPosition(100, 100); glutInitWindowSize(400, 400); #endif /* !OPENGL */ #endif /* !DISPLAY_FRAMES */ window_width = 400; window_height = 400; #if DISPLAY_FRAMES #if OPENGL window = glutCreateWindow("glut player"); #else // InitApplication ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; // thick frame? wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.lpszMenuName = NULL; wc.lpszClassName = CLASSNAME; if (!RegisterClassEx(&wc)) { // end of InitApplication printf("Window registration failed\n"); return 1; } window_style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS; // InitInstance window = CreateWindowEx(WS_EX_CLIENTEDGE, CLASSNAME, APPLICATIONNAME, window_style, CW_USEDEFAULT, CW_USEDEFAULT, window_width, window_height, NULL, NULL, hInstance, NULL); // InitInstance if (window == NULL) { printf("Window creation failed\n"); return 1; } // display and update window #endif #endif // DISPLAY_FRAMES oggplay_use_buffer(player, OGGPLAY_BUFFER_SIZE); audio_synch = CreateEvent(0, FALSE, FALSE, 0); sem = CreateSemaphore(NULL, (long)OGGPLAY_BUFFER_SIZE, (long)OGGPLAY_BUFFER_SIZE, NULL); InitializeCriticalSection(&waveCriticalSection); info.decode_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)drive_decoding, NULL, 0, &dec_id); #if !OPENGL info.display_thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)display_frame, NULL, 0, &disp_id); SetWindowLong(window, GWL_USERDATA, (long)&info); #endif #if DISPLAY_FRAMES #if OPENGL glutIdleFunc(&display_frame); glutDisplayFunc(&show_window); //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); //nice(5); glutMainLoop(); #else // process window messages while ((fGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 && fGotMessage != -1) { TranslateMessage(&msg); DispatchMessage(&msg); } DeleteCriticalSection(&waveCriticalSection); #endif #else while (1) { display_frame(NULL); } #endif /* !DISPLAY_FRAMES */ printf("there were %d frames\n", n_frames); #if !OPENGL return msg.wParam; // UNREFERENCED_PARAMETER(lpCmdLine); #endif }