Last active
December 5, 2020 17:51
-
-
Save flibitijibibo/9adafc4b9ed9f9b9b8c7 to your computer and use it in GitHub Desktop.
A quick and dirty application that lets me stream from an Intensity Pro into an SDL/OpenGL window.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* SDL_DeckLink - OpenGL DeckLink A/V Playback for GNU/Linux | |
* Written by Ethan "flibitijibibo" Lee | |
* http://www.flibitijibibo.com/ | |
* | |
* Build Instructions: | |
* g++ -O2 -o SDL_DeckLink SDL_DeckLink.cpp -lSDL2 -lGL -ldl -pthread | |
* | |
* Released under public domain. | |
* No warranty implied; use at your own risk. | |
* | |
* The fragment shader located at `shader_fragment` comes from an example found | |
* in the DeckLink SDK. This example is released under the BSD license. | |
* See the Blackmagic DeckLink SDK for details. | |
*/ | |
#define GL_GLEXT_PROTOTYPES | |
#include <SDL2/SDL.h> | |
#include <SDL2/SDL_opengl.h> | |
#include "DeckLinkAPI.h" | |
#include "DeckLinkAPIDispatch.cpp" | |
/* #define WIIU_GAMEPAD */ | |
#ifdef WIIU_GAMEPAD | |
#include <drc/c/streamer.h> | |
#endif | |
#define WINDOW_WIDTH 1280 | |
#define WINDOW_HEIGHT 720 | |
#define VIDEO_WIDTH 1280 | |
#define VIDEO_HEIGHT 720 | |
#define VIDEO_MODE bmdModeHD720p60 | |
/* #define DEBUG_GL_MODE */ | |
static const char *shader_vertex = | |
"#version 130\n" | |
"attribute vec2 pos;\n" | |
"attribute vec2 tex;\n" | |
"void main(void)\n" | |
"{\n" | |
" gl_Position = vec4(pos.xy, 0.0, 1.0);\n" | |
" gl_TexCoord[0].xy = tex;\n" | |
"}\n"; | |
static const char *shader_fragment = | |
"#version 130\n" | |
"uniform sampler2D UYVYtex;\n" | |
"void main(void)\n" | |
"{\n" | |
" float tx, ty, Y, Cb, Cr, r, g, b;\n" | |
" tx = gl_TexCoord[0].x;\n" | |
" ty = gl_TexCoord[0].y;\n" | |
" int true_width = textureSize(UYVYtex, 0).x * 2;\n" | |
" if (fract(floor(tx * true_width + 0.5) / 2.0) > 0.0)\n" | |
" Y = texture2D(UYVYtex, vec2(tx,ty)).a;\n" | |
" else\n" | |
" Y = texture2D(UYVYtex, vec2(tx,ty)).g;\n" | |
" Cb = texture2D(UYVYtex, vec2(tx,ty)).b;\n" | |
" Cr = texture2D(UYVYtex, vec2(tx,ty)).r;\n" | |
" Y = (Y * 256.0 - 16.0) / 219.0;\n" | |
" Cb = (Cb * 256.0 - 16.0) / 224.0 - 0.5;\n" | |
" Cr = (Cr * 256.0 - 16.0) / 224.0 - 0.5;\n" | |
" r = Y + 1.5748 * Cr;\n" | |
" g = Y - 0.1873 * Cb - 0.4681 * Cr;\n" | |
" b = Y + 1.8556 * Cb;\n" | |
" gl_FragColor = vec4(r, g, b, 0.7);\n" | |
"}\n"; | |
static const float vert_pos[8] = | |
{ | |
-1.0f, 1.0f, | |
1.0f, 1.0f, | |
-1.0f, -1.0f, | |
1.0f, -1.0f | |
}; | |
static const float vert_tex[8] = | |
{ | |
0.0f, 0.0f, | |
1.0f, 0.0f, | |
0.0f, 1.0f, | |
1.0f, 1.0f | |
}; | |
class DeckLinkInput : public IDeckLinkInputCallback | |
{ | |
public: | |
Uint16 YUVData[VIDEO_WIDTH * VIDEO_HEIGHT]; | |
SDL_AudioDeviceID audio; | |
DeckLinkInput() | |
{ | |
audio = 0; | |
} | |
virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( | |
IDeckLinkVideoInputFrame *videoFrame, | |
IDeckLinkAudioInputPacket *audioPacket | |
) { | |
void *frameData; | |
if (videoFrame != NULL) | |
{ | |
videoFrame->GetBytes(&frameData); | |
SDL_memcpy(YUVData, frameData, sizeof(YUVData)); | |
} | |
if (audioPacket != NULL && audio != 0) | |
{ | |
audioPacket->GetBytes(&frameData); | |
SDL_QueueAudio( | |
audio, | |
frameData, | |
audioPacket->GetSampleFrameCount() * 4 | |
); | |
} | |
return S_OK; | |
} | |
virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( | |
BMDVideoInputFormatChangedEvents notificationEvents, | |
IDeckLinkDisplayMode *newDisplayMode, | |
BMDDetectedVideoInputFormatFlags detectedSignalFlags | |
) { | |
return S_OK; | |
} | |
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID*) | |
{ | |
return E_NOINTERFACE; | |
} | |
virtual ULONG STDMETHODCALLTYPE AddRef() | |
{ | |
return 1; | |
} | |
virtual ULONG STDMETHODCALLTYPE Release() | |
{ | |
return 1; | |
} | |
}; | |
#ifdef DEBUG_GL_MODE | |
void GLDebugCallback( | |
GLenum source, | |
GLenum type, | |
GLuint id, | |
GLenum severity, | |
GLint length, | |
const GLchar *message, | |
const GLvoid *userParam | |
) { | |
printf("%s\n", message); | |
} | |
#endif | |
int main(int argc, char **argv) | |
{ | |
/* SDL2-specific variables */ | |
SDL_Window *window; | |
SDL_GLContext context; | |
SDL_AudioSpec spec; | |
SDL_Event evt; | |
Uint8 run = 1; | |
/* OpenGL-specific variables */ | |
GLuint texture; | |
GLuint vertShader; | |
GLuint fragShader; | |
GLint shaderLen; | |
GLuint shaderProgram; | |
/* DeckLink-specific variables */ | |
IDeckLinkIterator *deckLinkIterator; | |
IDeckLink *deckLinkInstance; | |
IDeckLinkInput *deckLinkInput; | |
DeckLinkInput deckLinkInputCallback; | |
#ifdef WIIU_GAMEPAD | |
unsigned char rgbaPixelData[WINDOW_WIDTH * WINDOW_HEIGHT * 4]; | |
drc_streamer *gamepad = drc_new_streamer(); | |
if (gamepad == NULL) | |
{ | |
printf("libdrc failed to open the GamePad.\n"); | |
return 0; | |
} | |
drc_start_streamer(gamepad); | |
#endif | |
/* Initialize DeckLink stream */ | |
deckLinkIterator = CreateDeckLinkIteratorInstance(); | |
if (deckLinkIterator == NULL) | |
{ | |
printf("DeckLink failed to initialize!\n"); | |
return 0; | |
} | |
if (deckLinkIterator->Next(&deckLinkInstance) != S_OK) | |
{ | |
printf("DeckLink failed to find a device!\n"); | |
return 0; | |
} | |
if (deckLinkInstance->QueryInterface( | |
IID_IDeckLinkInput, | |
(void**) &deckLinkInput | |
) != S_OK) { | |
printf("DeckLink input failed to open!\n"); | |
return 0; | |
} | |
if (deckLinkInput->EnableVideoInput( | |
VIDEO_MODE, | |
bmdFormat8BitYUV, | |
bmdVideoInputFlagDefault | |
) != S_OK) { | |
printf("DeckLink input failed to enable video input!\n"); | |
return 0; | |
} | |
if (deckLinkInput->EnableAudioInput( | |
bmdAudioSampleRate48kHz, | |
bmdAudioSampleType16bitInteger, | |
2 | |
) != S_OK) { | |
printf("DeckLink input failed to enable audio input!\n"); | |
return 0; | |
} | |
if (deckLinkInput->SetCallback(&deckLinkInputCallback) != S_OK) | |
{ | |
printf("DeckLink input failed to set input callback!\n"); | |
return 0; | |
} | |
if (deckLinkInput->StartStreams() != S_OK) | |
{ | |
printf("DeckLink input failed to start stream!\n"); | |
return 0; | |
} | |
/* Initialize SDL subsystems */ | |
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); | |
/* Create window and OpenGL context */ | |
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); | |
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); | |
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0); | |
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |
#ifdef DEBUG_GL_MODE | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); | |
#endif | |
window = SDL_CreateWindow( | |
"SDL_DeckLink", | |
SDL_WINDOWPOS_CENTERED, | |
SDL_WINDOWPOS_CENTERED, | |
WINDOW_WIDTH, | |
WINDOW_HEIGHT, | |
SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | |
); | |
context = SDL_GL_CreateContext(window); | |
SDL_GL_SetSwapInterval(-1); | |
/* Create audio output device */ | |
SDL_memset(&spec, '\0', sizeof(spec)); | |
spec.freq = 48000; | |
spec.format = AUDIO_S16; | |
spec.channels = 2; | |
spec.samples = 4096; | |
deckLinkInputCallback.audio = SDL_OpenAudioDevice( | |
NULL, | |
0, | |
&spec, | |
NULL, | |
0 | |
); | |
SDL_PauseAudioDevice(deckLinkInputCallback.audio, 0); | |
#ifdef DEBUG_GL_MODE | |
/* Add the ARB_debug_output callback */ | |
glDebugMessageCallbackARB((GLDEBUGPROCARB) GLDebugCallback, NULL); | |
glDebugMessageControlARB( | |
GL_DONT_CARE, | |
GL_DONT_CARE, | |
GL_DONT_CARE, | |
0, | |
NULL, | |
GL_TRUE | |
); | |
#endif | |
/* Prepare YUV texture */ | |
glGenTextures(1, &texture); | |
glBindTexture(GL_TEXTURE_2D, texture); | |
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 | |
); | |
glTexImage2D( | |
GL_TEXTURE_2D, | |
0, | |
GL_RGBA, | |
VIDEO_WIDTH / 2, | |
VIDEO_HEIGHT, | |
0, | |
GL_BGRA, | |
GL_UNSIGNED_INT_8_8_8_8_REV, | |
NULL | |
); | |
/* Prepare YUV->RGBA shader program */ | |
shaderLen = SDL_strlen(shader_vertex); | |
vertShader = glCreateShader(GL_VERTEX_SHADER); | |
glShaderSource(vertShader, 1, &shader_vertex, &shaderLen); | |
glCompileShader(vertShader); | |
shaderLen = SDL_strlen(shader_fragment); | |
fragShader = glCreateShader(GL_FRAGMENT_SHADER); | |
glShaderSource(fragShader, 1, &shader_fragment, &shaderLen); | |
glCompileShader(fragShader); | |
shaderProgram = glCreateProgram(); | |
glAttachShader(shaderProgram, vertShader); | |
glAttachShader(shaderProgram, fragShader); | |
glBindAttribLocation(shaderProgram, 0, "pos"); | |
glBindAttribLocation(shaderProgram, 1, "tex"); | |
glLinkProgram(shaderProgram); | |
glDeleteShader(vertShader); | |
glDeleteShader(fragShader); | |
glUseProgram(shaderProgram); | |
glUniform1i(glGetUniformLocation(shaderProgram, "UYVYtex"), 0); | |
/* Set the vertex pointers, which will always be the same */ | |
glVertexAttribPointer( | |
0, | |
2, | |
GL_FLOAT, | |
GL_FALSE, | |
sizeof(float) * 2, | |
vert_pos | |
); | |
glVertexAttribPointer( | |
1, | |
2, | |
GL_FLOAT, | |
GL_FALSE, | |
sizeof(float) * 2, | |
vert_tex | |
); | |
glEnableVertexAttribArray(0); | |
glEnableVertexAttribArray(1); | |
/* Reset the viewport, just in case it's not right already */ | |
glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); | |
while (run) | |
{ | |
/* Exit upon user request */ | |
while (SDL_PollEvent(&evt) > 0) | |
{ | |
if (evt.type == SDL_QUIT) | |
{ | |
run = 0; | |
} | |
} | |
/* Convert current YUV image to RGBA, present result */ | |
glTexSubImage2D( | |
GL_TEXTURE_2D, | |
0, | |
0, | |
0, | |
VIDEO_WIDTH / 2, | |
VIDEO_HEIGHT, | |
GL_BGRA, | |
GL_UNSIGNED_INT_8_8_8_8_REV, | |
deckLinkInputCallback.YUVData | |
); | |
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
#ifdef WIIU_GAMEPAD | |
glReadPixels( | |
0, 0, | |
WINDOW_WIDTH, | |
WINDOW_HEIGHT, | |
GL_RGBA, | |
GL_UNSIGNED_BYTE, | |
rgbaPixelData | |
); | |
drc_push_vid_frame( | |
gamepad, | |
rgbaPixelData, | |
sizeof(rgbaPixelData), | |
WINDOW_WIDTH, | |
WINDOW_HEIGHT, | |
DRC_RGBA, | |
DRC_FLIP_VERTICALLY | |
); | |
#endif | |
SDL_GL_SwapWindow(window); | |
} | |
/* Clean up. We out. */ | |
#ifdef WIIU_GAMEPAD | |
drc_stop_streamer(gamepad); | |
#endif | |
deckLinkInput->StopStreams(); | |
glDeleteProgram(shaderProgram); | |
glDeleteTextures(1, &texture); | |
SDL_CloseAudioDevice(deckLinkInputCallback.audio); | |
SDL_GL_DeleteContext(context); | |
SDL_DestroyWindow(window); | |
SDL_Quit(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment