/* Theorafile - Ogg Theora Video Decoder Library * * Copyright (c) 2017-2024 Ethan Lee. * Based on TheoraPlay, Copyright (c) 2011-2016 Ryan C. Gordon. * * This software is provided 'as-is', without any express or implied warranty. * In no event will the authors be held liable for any damages arising from * the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software in a * product, an acknowledgment in the product documentation would be * appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * * Ethan "flibitijibibo" Lee * */ #include #include #include #include "theorafile.h" /* GL Function typedefs */ #define GL_PROC(ret, func, parms) \ typedef ret (GLAPIENTRY *glfntype_##func) parms; #include "glfuncs.h" #undef GL_PROC /* GL Function declarations */ #define GL_PROC(ret, func, parms) \ glfntype_##func INTERNAL_##func; #include "glfuncs.h" #undef GL_PROC static const GLchar *GLVert = "#version 110\n" "attribute vec2 pos;\n" "attribute vec2 tex;\n" "void main() {\n" "gl_Position = vec4(pos.xy, 0.0, 1.0);\n" "gl_TexCoord[0].xy = tex;\n" "}\n"; /* This shader was originally from SDL 1.3! * It has been modified to use color conversion for HD formats: * http://www.equasys.de/colorconversion.html * http://www.equasys.de/colorformat.html */ static const GLchar *GLFrag = "#version 110\n" "uniform sampler2D samp0;\n" "uniform sampler2D samp1;\n" "uniform sampler2D samp2;\n" "const vec3 offset = vec3(-0.0625, -0.5, -0.5);\n" "const vec3 Rcoeff = vec3(1.164, 0.000, 1.793);\n" "const vec3 Gcoeff = vec3(1.164, -0.213, -0.533);\n" "const vec3 Bcoeff = vec3(1.164, 2.112, 0.000);\n" "void main() {\n" " vec2 tcoord;\n" " vec3 yuv, rgb;\n" " tcoord = gl_TexCoord[0].xy;\n" " yuv.x = texture2D(samp0, tcoord).r;\n" " yuv.y = texture2D(samp1, tcoord).r;\n" " yuv.z = texture2D(samp2, tcoord).r;\n" " yuv += offset;\n" " rgb.r = dot(yuv, Rcoeff);\n" " rgb.g = dot(yuv, Gcoeff);\n" " rgb.b = dot(yuv, Bcoeff);\n" " gl_FragColor = vec4(rgb, 1.0);\n" "}\n"; void SDLCALL AudioCallback(void *userdata, Uint8* stream, int len) { const int samples = len / 4; int read = tf_readaudio((OggTheora_File*) userdata, (float*) stream, samples); if (read < samples) { SDL_memset(stream + read * 4, '\0', (samples - read) * 4); } } int main(int argc, char **argv) { /* SDL variables */ SDL_Window *window; SDL_GLContext context; SDL_AudioDeviceID audio; SDL_AudioSpec spec; SDL_Event evt; Uint64 timeStart, timeCurrent; Uint8 run = 1; /* Theorafile variables */ OggTheora_File fileIn; int width, height, uvWidth, uvHeight; int channels, samplerate; double fps; th_pixel_fmt fmt; int curframe = 0, thisframe, newframe; char *frame = NULL; /* OpenGL variables */ GLuint yuvTextures[3]; GLuint vertex = 0; GLuint fragment = 0; GLuint program = 0; GLint shaderlen = 0; /* Vertex client arrays */ static struct { float pos[2]; float tex[2]; } verts[4] = { { { -1.0f, 1.0f }, { 0.0f, 0.0f } }, { { 1.0f, 1.0f }, { 1.0f, 0.0f } }, { { -1.0f, -1.0f }, { 0.0f, 1.0f } }, { { 1.0f, -1.0f }, { 1.0f, 1.0f } } }; /* We need a file name! */ if (argc < 2 || argc > 2) { SDL_Log("Need a file name!\n"); return 1; } /* Open the Theora file */ if (tf_fopen(argv[1], &fileIn) < 0) { SDL_Log("Failed to open file.\n"); return 1; } /* This is a video test, people! */ if (!tf_hasvideo(&fileIn)) { SDL_Log("No video!\n"); return 1; } /* Get the video metadata, allocate first frame */ tf_videoinfo(&fileIn, &width, &height, &fps, &fmt); frame = (char*) SDL_malloc(width * height * 2); if (fmt == TH_PF_420) { /* Subsampled in both dimensions */ uvWidth = width / 2; uvHeight = height / 2; } else if (fmt == TH_PF_422) { /* Subsampled only horizontally */ uvWidth = width / 2; uvHeight = height; } else { /* No subsampling at all... */ uvWidth = width; uvHeight = height; } while (!tf_readvideo(&fileIn, frame, 1)); /* Create window (and audio device, if applicable) */ SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); window = SDL_CreateWindow( "Theorafile Test", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL ); context = SDL_GL_CreateContext(window); SDL_GL_SetSwapInterval(1); /* GL function loading */ #define GL_PROC(ret, func, parms) \ INTERNAL_##func = (glfntype_##func) SDL_GL_GetProcAddress(#func); #include "glfuncs.h" #undef GL_PROC /* Remap GL function names to internal entry points */ #include "glmacros.h" if (tf_hasaudio(&fileIn)) { /* Get the audio metadata, allocate queue */ tf_audioinfo(&fileIn, &channels, &samplerate); SDL_zero(spec); spec.freq = samplerate; spec.format = AUDIO_F32; spec.channels = channels; spec.samples = 4096; spec.callback = AudioCallback; spec.userdata = &fileIn; audio = SDL_OpenAudioDevice( NULL, 0, &spec, NULL, 0 ); SDL_PauseAudioDevice(audio, 0); } /* Initial GL state */ glDepthMask(GL_FALSE); glDisable(GL_DEPTH_TEST); glDisable(GL_ALPHA_TEST); glDisable(GL_BLEND); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* YUV buffers */ glGenTextures(3, yuvTextures); #define GEN_TEXTURE(index, w, h, ptr) \ glActiveTexture(GL_TEXTURE0 + index); \ glBindTexture(GL_TEXTURE_2D, yuvTextures[index]); \ glTexImage2D( \ GL_TEXTURE_2D, \ 0, \ GL_LUMINANCE8, \ w, \ h, \ 0, \ GL_LUMINANCE, \ GL_UNSIGNED_BYTE, \ ptr \ ); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); \ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); GEN_TEXTURE( 0, width, height, frame ) GEN_TEXTURE( 1, uvWidth, uvHeight, frame + (width * height) ) GEN_TEXTURE( 2, uvWidth, uvHeight, frame + (width * height) + (uvWidth * uvHeight) ) #undef GEN_TEXTURE /* Vertex shader... */ shaderlen = (GLint) SDL_strlen(GLVert); vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &GLVert, &shaderlen); glCompileShader(vertex); /* Fragment shader... */ shaderlen = (GLint) SDL_strlen(GLFrag); fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &GLFrag, &shaderlen); glCompileShader(fragment); /* Program object... */ program = glCreateProgram(); glAttachShader(program, vertex); glAttachShader(program, fragment); glBindAttribLocation(program, 0, "pos"); glBindAttribLocation(program, 1, "tex"); glLinkProgram(program); glDeleteShader(vertex); glDeleteShader(fragment); /* ... Finally. */ glUseProgram(program); glUniform1i(glGetUniformLocation(program, "samp0"), 0); glUniform1i(glGetUniformLocation(program, "samp1"), 1); glUniform1i(glGetUniformLocation(program, "samp2"), 2); /* Vertex buffers */ glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof (verts[0]), &verts[0].pos[0]); glVertexAttribPointer(1, 2, GL_FLOAT, 0, sizeof (verts[0]), &verts[0].tex[0]); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); timeStart = SDL_GetPerformanceCounter(); while (run) { while (SDL_PollEvent(&evt)) { if (evt.type == SDL_QUIT) { run = 0; } else if (evt.type == SDL_KEYDOWN) { if (evt.key.keysym.sym == SDLK_SPACE) { /* Slowdown simulator */ SDL_Delay(1000); } else if ( evt.key.keysym.sym >= SDLK_1 && evt.key.keysym.sym <= SDLK_9 ) { SDL_LockAudioDevice(audio); tf_setaudiotrack( &fileIn, evt.key.keysym.sym - SDLK_1 ); SDL_UnlockAudioDevice(audio); } } } /* Loop this video! */ SDL_LockAudioDevice(audio); if (tf_eos(&fileIn)) { tf_reset(&fileIn); } SDL_UnlockAudioDevice(audio); /* Based on when we started, what frame should we be on? */ timeCurrent = SDL_GetPerformanceCounter(); thisframe = (int) ( (double) (timeCurrent - timeStart) / (double) SDL_GetPerformanceFrequency() * fps ); if (thisframe > curframe) { /* Keep reading frames until we're caught up */ SDL_LockAudioDevice(audio); newframe = tf_readvideo(&fileIn, frame, thisframe - curframe); SDL_UnlockAudioDevice(audio); curframe = thisframe; /* Only update the textures if we need to! */ if (newframe) { glActiveTexture(GL_TEXTURE0); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, width, height, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame ); glActiveTexture(GL_TEXTURE1); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, uvWidth, uvHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame + (width * height) ); glActiveTexture(GL_TEXTURE2); glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, uvWidth, uvHeight, GL_LUMINANCE, GL_UNSIGNED_BYTE, frame + (width * height) + (uvWidth * uvHeight) ); } } /* Draw! */ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); SDL_GL_SwapWindow(window); } /* Clean up. We out. */ glDeleteProgram(program); glDeleteTextures(3, yuvTextures); SDL_free(frame); if (tf_hasaudio(&fileIn)) { SDL_CloseAudioDevice(audio); } tf_close(&fileIn); SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); SDL_Quit(); SDL_Log("Test complete.\n"); return 0; }