From 09a02c0adfe8d4ceff0f60f664f724e5f6bd5341 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas Date: Sun, 10 Feb 2013 02:19:40 -0600 Subject: [PATCH] Adding the initial version of the decklink writer, and a fully working (full circle) blackmagic example executabe, which processes real-time HD video and output's it via HDMI. --- .../{DecklinkCapture.h => DecklinkInput.h} | 11 +- include/DecklinkOutput.h | 73 +++++ include/DecklinkReader.h | 7 +- include/DecklinkWriter.h | 88 ++++++ include/OpenShot.h | 2 +- src/CMakeLists.txt | 6 +- ...{DecklinkCapture.cpp => DecklinkInput.cpp} | 31 +-- src/DecklinkOutput.cpp | 234 ++++++++++++++++ src/DecklinkReader.cpp | 21 +- src/DecklinkWriter.cpp | 223 ++++++++++++++++ src/Frame.cpp | 8 +- src/Main_Blackmagic.cpp | 252 ++---------------- src/openshot.i | 2 + 13 files changed, 694 insertions(+), 264 deletions(-) rename include/{DecklinkCapture.h => DecklinkInput.h} (79%) create mode 100644 include/DecklinkOutput.h create mode 100644 include/DecklinkWriter.h rename src/{DecklinkCapture.cpp => DecklinkInput.cpp} (85%) create mode 100644 src/DecklinkOutput.cpp create mode 100644 src/DecklinkWriter.cpp diff --git a/include/DecklinkCapture.h b/include/DecklinkInput.h similarity index 79% rename from include/DecklinkCapture.h rename to include/DecklinkInput.h index fc6f58ec..ebc7ebba 100644 --- a/include/DecklinkCapture.h +++ b/include/DecklinkInput.h @@ -1,5 +1,5 @@ -#ifndef __CAPTURE_H__ -#define __CAPTURE_H__ +#ifndef OPENSHOT_DECKLINK_INPUT_H +#define OPENSHOT_DECKLINK_INPUT_H #include #include @@ -11,11 +11,10 @@ #include #include "DeckLinkAPI.h" -#include "../include/DecklinkCapture.h" #include "../include/Frame.h" -class DeckLinkCaptureDelegate : public IDeckLinkInputCallback +class DeckLinkInputDelegate : public IDeckLinkInputCallback { public: pthread_cond_t* sleepCond; @@ -30,8 +29,8 @@ public: IDeckLinkOutput *deckLinkOutput; IDeckLinkVideoConversion *deckLinkConverter; - DeckLinkCaptureDelegate(pthread_cond_t* m_sleepCond, IDeckLinkOutput* deckLinkOutput, IDeckLinkVideoConversion* deckLinkConverter); - ~DeckLinkCaptureDelegate(); + DeckLinkInputDelegate(pthread_cond_t* m_sleepCond, IDeckLinkOutput* deckLinkOutput, IDeckLinkVideoConversion* deckLinkConverter); + ~DeckLinkInputDelegate(); virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; } virtual ULONG STDMETHODCALLTYPE AddRef(void); diff --git a/include/DecklinkOutput.h b/include/DecklinkOutput.h new file mode 100644 index 00000000..928c3bb5 --- /dev/null +++ b/include/DecklinkOutput.h @@ -0,0 +1,73 @@ +#ifndef OPENSHOT_DECKLINK_OUTPUT_H +#define OPENSHOT_DECKLINK_OUTPUT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Magick++.h" + +#include "DeckLinkAPI.h" +#include "../include/Frame.h" + +enum OutputSignal { + kOutputSignalPip = 0, + kOutputSignalDrop = 1 +}; + +class DeckLinkOutputDelegate : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback +{ +protected: + unsigned long m_totalFramesScheduled; + OutputSignal m_outputSignal; + void* m_audioBuffer; + unsigned long m_audioBufferSampleLength; + unsigned long m_audioBufferOffset; + unsigned long m_audioChannelCount; + BMDAudioSampleRate m_audioSampleRate; + unsigned long m_audioSampleDepth; + unsigned long audioSamplesPerFrame; + unsigned long m_framesPerSecond; + + BMDTimeValue frameRateDuration, frameRateScale; + + // Queue of raw video frames + deque final_frames; + deque > raw_video_frames; + + // Convert between YUV and RGB + IDeckLinkOutput *deckLinkOutput; + IDeckLinkDisplayMode *displayMode; + +public: + DeckLinkOutputDelegate(IDeckLinkDisplayMode *displayMode, IDeckLinkOutput* deckLinkOutput); + ~DeckLinkOutputDelegate(); + + // *** DeckLink API implementation of IDeckLinkVideoOutputCallback IDeckLinkAudioOutputCallback *** // + // IUnknown needs only a dummy implementation + virtual HRESULT STDMETHODCALLTYPE QueryInterface (REFIID iid, LPVOID *ppv) {return E_NOINTERFACE;} + virtual ULONG STDMETHODCALLTYPE AddRef () {return 1;} + virtual ULONG STDMETHODCALLTYPE Release () {return 1;} + + virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result); + virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped (); + + virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples (bool preroll); + + /// Schedule the next frame + void ScheduleNextFrame(bool prerolling); + + /// Custom method to write new frames + void WriteFrame(tr1::shared_ptr frame); + +private: + ULONG m_refCount; + pthread_mutex_t m_mutex; +}; + + +#endif diff --git a/include/DecklinkReader.h b/include/DecklinkReader.h index 8270e572..0b88b199 100644 --- a/include/DecklinkReader.h +++ b/include/DecklinkReader.h @@ -25,7 +25,7 @@ #include "Cache.h" #include "Exceptions.h" #include "Frame.h" -#include "DecklinkCapture.h" +#include "DecklinkInput.h" using namespace std; @@ -49,7 +49,7 @@ namespace openshot pthread_mutex_t sleepMutex; pthread_cond_t sleepCond; IDeckLinkIterator *deckLinkIterator; - DeckLinkCaptureDelegate *delegate; + DeckLinkInputDelegate *delegate; IDeckLinkDisplayMode *displayMode; BMDVideoInputFlags inputFlags; BMDDisplayMode selectedDisplayMode; @@ -63,12 +63,13 @@ namespace openshot int g_audioChannels; int g_audioSampleDepth; int g_maxFrames; + int device; public: /// Constructor for DecklinkReader. This automatically opens the device and loads /// the first second of video, or it throws one of the following exceptions. - DecklinkReader(int video_mode, int pixel_format, int channels, int sample_depth) throw(DecklinkError); + DecklinkReader(int device, int video_mode, int pixel_format, int channels, int sample_depth) throw(DecklinkError); /// Close the device and video stream void Close(); diff --git a/include/DecklinkWriter.h b/include/DecklinkWriter.h new file mode 100644 index 00000000..ca5439a1 --- /dev/null +++ b/include/DecklinkWriter.h @@ -0,0 +1,88 @@ +#ifndef OPENSHOT_DECKLINK_WRITER_H +#define OPENSHOT_DECKLINK_WRITER_H + +/** + * \file + * \brief Header file for ImageReader class + * \author Copyright (c) 2011 Jonathan Thomas + */ + +#include "FileWriterBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Magick++.h" +#include "Cache.h" +#include "Exceptions.h" +#include "Frame.h" +#include "DecklinkOutput.h" + +using namespace std; + +namespace openshot +{ + + /** + * \brief This class uses the Blackmagic Decklink libraries, to open video streams on Blackmagic devices, and return + * openshot::Frame objects containing the image and audio data. + */ + class DecklinkWriter : public FileWriterBase + { + private: + bool is_open; + + IDeckLink *deckLink; + IDeckLinkDisplayModeIterator *displayModeIterator; + IDeckLinkOutput *deckLinkOutput; + IDeckLinkVideoConversion *m_deckLinkConverter; + pthread_mutex_t sleepMutex; + pthread_cond_t sleepCond; + IDeckLinkIterator *deckLinkIterator; + DeckLinkOutputDelegate *delegate; + IDeckLinkDisplayMode *displayMode; + BMDVideoInputFlags inputFlags; + BMDDisplayMode selectedDisplayMode; + BMDPixelFormat pixelFormat; + int displayModeCount; + int exitStatus; + int ch; + bool foundDisplayMode; + HRESULT result; + int g_videoModeIndex; + int g_audioChannels; + int g_audioSampleDepth; + int g_maxFrames; + int device; + + public: + + /// Constructor for DecklinkWriter. This automatically opens the device or it + /// throws one of the following exceptions. + DecklinkWriter(int device, int video_mode, int pixel_format, int channels, int sample_depth) throw(DecklinkError); + + /// Close the device and video stream + void Close(); + + /// This method is required for all derived classes of FileWriterBase. Write a Frame to the video file. + void WriteFrame(tr1::shared_ptr frame); + + /// This method is required for all derived classes of FileWriterBase. Write a block of frames from a reader. + void WriteFrame(FileReaderBase* reader, int start, int length); + + /// Open device and video stream - which is called by the constructor automatically + void Open() throw(DecklinkError); + }; + +} + +#endif diff --git a/include/OpenShot.h b/include/OpenShot.h index 00c5343e..743cc314 100644 --- a/include/OpenShot.h +++ b/include/OpenShot.h @@ -31,8 +31,8 @@ #include "Clip.h" #include "Coordinate.h" #ifdef USE_BLACKMAGIC - #include "DecklinkCapture.h" #include "DecklinkReader.h" + #include "DecklinkWriter.h" #endif #include "DummyReader.h" #include "Exceptions.h" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7253c0d..61768dab 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,8 +77,10 @@ SET ( OPENSHOT_SOURCE_FILES IF (BLACKMAGIC_FOUND) SET ( OPENSHOT_SOURCE_FILES ${OPENSHOT_SOURCE_FILES} - DecklinkCapture.cpp - DecklinkReader.cpp ) + DecklinkInput.cpp + DecklinkReader.cpp + DecklinkOutput.cpp + DecklinkWriter.cpp) ENDIF (BLACKMAGIC_FOUND) diff --git a/src/DecklinkCapture.cpp b/src/DecklinkInput.cpp similarity index 85% rename from src/DecklinkCapture.cpp rename to src/DecklinkInput.cpp index b3bd2f58..e26f20bc 100644 --- a/src/DecklinkCapture.cpp +++ b/src/DecklinkInput.cpp @@ -25,11 +25,11 @@ ** -LICENSE-END- */ -#include "../include/DecklinkCapture.h" +#include "../include/DecklinkInput.h" using namespace std; -DeckLinkCaptureDelegate::DeckLinkCaptureDelegate(pthread_cond_t* m_sleepCond, IDeckLinkOutput* m_deckLinkOutput, IDeckLinkVideoConversion* m_deckLinkConverter) +DeckLinkInputDelegate::DeckLinkInputDelegate(pthread_cond_t* m_sleepCond, IDeckLinkOutput* m_deckLinkOutput, IDeckLinkVideoConversion* m_deckLinkConverter) : m_refCount(0), g_timecodeFormat(0), frameCount(0) { sleepCond = m_sleepCond; @@ -39,12 +39,12 @@ DeckLinkCaptureDelegate::DeckLinkCaptureDelegate(pthread_cond_t* m_sleepCond, ID pthread_mutex_init(&m_mutex, NULL); } -DeckLinkCaptureDelegate::~DeckLinkCaptureDelegate() +DeckLinkInputDelegate::~DeckLinkInputDelegate() { pthread_mutex_destroy(&m_mutex); } -ULONG DeckLinkCaptureDelegate::AddRef(void) +ULONG DeckLinkInputDelegate::AddRef(void) { pthread_mutex_lock(&m_mutex); m_refCount++; @@ -53,7 +53,7 @@ ULONG DeckLinkCaptureDelegate::AddRef(void) return (ULONG)m_refCount; } -ULONG DeckLinkCaptureDelegate::Release(void) +ULONG DeckLinkInputDelegate::Release(void) { pthread_mutex_lock(&m_mutex); m_refCount--; @@ -68,11 +68,11 @@ ULONG DeckLinkCaptureDelegate::Release(void) return (ULONG)m_refCount; } -tr1::shared_ptr DeckLinkCaptureDelegate::GetFrame(int requested_frame) +tr1::shared_ptr DeckLinkInputDelegate::GetFrame(int requested_frame) { tr1::shared_ptr f; - #pragma omp critical (blackmagic_queue) + #pragma omp critical (blackmagic_input_queue) { if (final_frames.size() > 0) { @@ -101,7 +101,7 @@ tr1::shared_ptr DeckLinkCaptureDelegate::GetFrame(int requested return f; } -HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioFrame) +HRESULT DeckLinkInputDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame* videoFrame, IDeckLinkAudioInputPacket* audioFrame) { // Handle Video Frame if(videoFrame) @@ -161,9 +161,9 @@ HRESULT DeckLinkCaptureDelegate::VideoInputFrameArrived(IDeckLinkVideoInputFrame //omp_set_num_threads(1); omp_set_nested(true); -#pragma omp parallel +#pragma xxx omp parallel { -#pragma omp single +#pragma xxx omp single { // Loop through each queued image frame while (!raw_video_frames.empty()) @@ -177,7 +177,7 @@ omp_set_nested(true); IDeckLinkVideoConversion *copy_deckLinkConverter(deckLinkConverter); unsigned long copy_frameCount(frameCount); - #pragma omp task firstprivate(copy_deckLinkOutput, copy_deckLinkConverter, frame, copy_frameCount) + #pragma xxx omp task firstprivate(copy_deckLinkOutput, copy_deckLinkConverter, frame, copy_frameCount) { // *********** CONVERT YUV source frame to RGB ************ void *frameBytes; @@ -212,16 +212,17 @@ omp_set_nested(true); // Add Image data to openshot frame f->AddImage(width, height, "ARGB", Magick::CharPixel, (uint8_t*)frameBytes); + f->TransparentColors("#737e72", 20.0); - #pragma omp critical (blackmagic_queue) + #pragma omp critical (blackmagic_input_queue) { // Add to final queue final_frames.push_back(f); // Don't keep too many frames (remove old frames) - while (final_frames.size() > 20) + //while (final_frames.size() > 20) // Remove oldest frame - final_frames.pop_front(); + // final_frames.pop_front(); } @@ -260,7 +261,7 @@ omp_set_nested(true); return S_OK; } -HRESULT DeckLinkCaptureDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents events, IDeckLinkDisplayMode *mode, BMDDetectedVideoInputFormatFlags) +HRESULT DeckLinkInputDelegate::VideoInputFormatChanged(BMDVideoInputFormatChangedEvents events, IDeckLinkDisplayMode *mode, BMDDetectedVideoInputFormatFlags) { return S_OK; } diff --git a/src/DecklinkOutput.cpp b/src/DecklinkOutput.cpp new file mode 100644 index 00000000..65d20471 --- /dev/null +++ b/src/DecklinkOutput.cpp @@ -0,0 +1,234 @@ +/* -LICENSE-START- +** Copyright (c) 2009 Blackmagic Design +** +** Permission is hereby granted, free of charge, to any person or organization +** obtaining a copy of the software and accompanying documentation covered by +** this license (the "Software") to use, reproduce, display, distribute, +** execute, and transmit the Software, and to prepare derivative works of the +** Software, and to permit third-parties to whom the Software is furnished to +** do so, all subject to the following: +** +** The copyright notices in the Software and this entire statement, including +** the above license grant, this restriction and the following disclaimer, +** must be included in all copies of the Software, in whole or in part, and +** all derivative works of the Software, unless such copies or derivative +** works are solely in the form of machine-executable object code generated by +** a source language processor. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +** DEALINGS IN THE SOFTWARE. +** -LICENSE-END- +*/ + +#include "../include/DecklinkOutput.h" + +using namespace std; + +DeckLinkOutputDelegate::DeckLinkOutputDelegate(IDeckLinkDisplayMode *displayMode, IDeckLinkOutput* m_deckLinkOutput) + : m_refCount(0), displayMode(displayMode) +{ + deckLinkOutput = m_deckLinkOutput; + + m_totalFramesScheduled = 0; + m_audioChannelCount = 2; + m_audioSampleRate = bmdAudioSampleRate48kHz; + m_audioSampleDepth = 16; + m_outputSignal = kOutputSignalDrop; + + // Get framerate + displayMode->GetFrameRate(&frameRateDuration, &frameRateScale); + m_framesPerSecond = (unsigned long)((frameRateScale + (frameRateDuration-1)) / frameRateDuration); + + // Allocate audio array + m_audioBufferSampleLength = (unsigned long)((m_framesPerSecond * m_audioSampleRate * frameRateDuration) / frameRateScale); + m_audioBuffer = valloc(m_audioBufferSampleLength * m_audioChannelCount * (m_audioSampleDepth / 8)); + + // Zero the buffer (interpreted as audio silence) + memset(m_audioBuffer, 0x0, (m_audioBufferSampleLength * m_audioChannelCount * m_audioSampleDepth/8)); + audioSamplesPerFrame = (unsigned long)((m_audioSampleRate * frameRateDuration) / frameRateScale); + + pthread_mutex_init(&m_mutex, NULL); +} + +DeckLinkOutputDelegate::~DeckLinkOutputDelegate() +{ + cout << "DESTRUCTOR!!!" << endl; + pthread_mutex_destroy(&m_mutex); +} + +/************************* DeckLink API Delegate Methods *****************************/ +HRESULT DeckLinkOutputDelegate::ScheduledFrameCompleted (IDeckLinkVideoFrame* completedFrame, BMDOutputFrameCompletionResult result) +{ + // Skip ahead, if frame was not shown + if (result != bmdOutputFrameCompleted) + m_totalFramesScheduled++; + + // When a video frame has been released by the API, schedule another video frame to be output + ScheduleNextFrame(false); + + return S_OK; +} + +HRESULT DeckLinkOutputDelegate::ScheduledPlaybackHasStopped () +{ + cout << "PLAYBACK HAS STOPPED!!!" << endl; + return S_OK; +} + +HRESULT DeckLinkOutputDelegate::RenderAudioSamples (bool preroll) +{ + // Provide further audio samples to the DeckLink API until our preferred buffer waterlevel is reached + const unsigned long kAudioWaterlevel = 48000; + unsigned int bufferedSamples; + + // Try to maintain the number of audio samples buffered in the API at a specified waterlevel + if ((deckLinkOutput->GetBufferedAudioSampleFrameCount(&bufferedSamples) == S_OK) && (bufferedSamples < kAudioWaterlevel)) + { + unsigned int samplesToEndOfBuffer; + unsigned int samplesToWrite; + unsigned int samplesWritten; + + samplesToEndOfBuffer = (m_audioBufferSampleLength - m_audioBufferOffset); + samplesToWrite = (kAudioWaterlevel - bufferedSamples); + if (samplesToWrite > samplesToEndOfBuffer) + samplesToWrite = samplesToEndOfBuffer; + + if (deckLinkOutput->ScheduleAudioSamples((void*)((unsigned long)m_audioBuffer + (m_audioBufferOffset * m_audioChannelCount * m_audioSampleDepth/8)), samplesToWrite, 0, 0, &samplesWritten) == S_OK) + { + m_audioBufferOffset = ((m_audioBufferOffset + samplesWritten) % m_audioBufferSampleLength); + } + } + + + if (preroll) + { + // Start audio and video output + deckLinkOutput->StartScheduledPlayback(0, 100, 1.0); + } + + return S_OK; +} + +// Schedule the next frame +void DeckLinkOutputDelegate::ScheduleNextFrame(bool prerolling) +{ + #pragma omp critical (blackmagic_output_queue) + { + // Get oldest frame (if any) + if (final_frames.size() > 0) + { + // Schedule the next frame + IDeckLinkMutableVideoFrame *m_rgbFrame = final_frames.front(); + final_frames.pop_front(); // remove this frame from the queue + + if (deckLinkOutput->ScheduleVideoFrame(m_rgbFrame, (m_totalFramesScheduled * frameRateDuration), frameRateDuration, frameRateScale) != S_OK) + cout << "ScheduleVideoFrame FAILED!!! " << m_totalFramesScheduled << endl; + + // Update the timestamp (regardless of previous frame's success) + m_totalFramesScheduled += 1; + + // Release frame + m_rgbFrame->Release(); + } + } // critical +} + +void DeckLinkOutputDelegate::WriteFrame(tr1::shared_ptr frame) +{ + + #pragma omp critical (blackmagic_output_queue) + // Add raw OpenShot frame object + raw_video_frames.push_back(frame); + + + // Process frames once we have a few (to take advantage of multiple threads) + if (raw_video_frames.size() >= omp_get_num_procs()) + { + + //omp_set_num_threads(1); + omp_set_nested(true); + #pragma xxx omp parallel + { + #pragma xxx omp single + { + // Loop through each queued image frame + while (!raw_video_frames.empty()) + { + // Get front frame (from the queue) + tr1::shared_ptr frame = raw_video_frames.front(); + raw_video_frames.pop_front(); + + + // Create a new RGB frame object + IDeckLinkMutableVideoFrame *m_rgbFrame = NULL; + + while (deckLinkOutput->CreateVideoFrame( + frame->GetWidth(), + frame->GetHeight(), + frame->GetWidth() * 4, + bmdFormat8BitARGB, + bmdFrameFlagDefault, + &m_rgbFrame) != S_OK) + { + // keep trying + usleep(1000 * 10); + } + + #pragma xxx omp task firstprivate(m_rgbFrame, frame) + { + // *********** CONVERT YUV source frame to RGB ************ + void *frameBytes; + void *audioFrameBytes; + + int width = frame->GetWidth(); + int height = frame->GetHeight(); + + // Get RGB Byte array + m_rgbFrame->GetBytes(&frameBytes); + uint8_t *castBytes = (uint8_t *) frameBytes; + + // Get a list of pixels in our frame's image. Each pixel is represented by + // a PixelPacket struct, which has 4 properties: .red, .blue, .green, .alpha + const Magick::PixelPacket *pixel_packets = frame->GetPixels(); + + // loop through ImageMagic pixel structs, and put the colors in a regular array, and move the + // colors around to match the Decklink order (ARGB). + int numBytes = m_rgbFrame->GetRowBytes() * height; + for (int packet = 0, row = 0; row < numBytes; packet++, row+=4) + { + // Update buffer (which is already linked to the AVFrame: pFrameRGB) + castBytes[row] = 255; // alpha + castBytes[row+1] = pixel_packets[packet].red; + castBytes[row+2] = pixel_packets[packet].green; + castBytes[row+3] = pixel_packets[packet].blue; + } + + // Add processed RGB frame to final_frames + final_frames.push_back(m_rgbFrame); + } // end task + + } // end while + } // omp single + } // omp parallel + +#pragma xxx omp critical (blackmagic_output_queue) +{ + //cout << "final_frames.size(): " << final_frames.size() << ", raw_video_frames.size(): " << raw_video_frames.size() << endl; + // Don't keep too many frames (remove old frames) + while (final_frames.size() > 15) + { + cout << "Too many, so remove some..." << endl; + // Remove oldest frame + final_frames.front()->Release(); + final_frames.pop_front(); + } +} + + } // if + +} diff --git a/src/DecklinkReader.cpp b/src/DecklinkReader.cpp index ab06770f..fcbf43f0 100644 --- a/src/DecklinkReader.cpp +++ b/src/DecklinkReader.cpp @@ -2,8 +2,8 @@ using namespace openshot; -DecklinkReader::DecklinkReader(int video_mode, int pixel_format, int channels, int sample_depth) throw(DecklinkError) - : is_open(false), g_videoModeIndex(video_mode), g_audioChannels(channels), g_audioSampleDepth(sample_depth) +DecklinkReader::DecklinkReader(int device, int video_mode, int pixel_format, int channels, int sample_depth) throw(DecklinkError) + : device(device), is_open(false), g_videoModeIndex(video_mode), g_audioChannels(channels), g_audioSampleDepth(sample_depth) { // Init FileInfo struct (clear all values) InitFileInfo(); @@ -40,10 +40,17 @@ void DecklinkReader::Open() throw(DecklinkError) if (!deckLinkIterator) throw DecklinkError("This application requires the DeckLink drivers installed."); - /* Connect to the first DeckLink instance */ - result = deckLinkIterator->Next(&deckLink); - if (result != S_OK) - throw DecklinkError("No DeckLink PCI cards found."); + /* Connect to a DeckLink instance */ + for (int device_count = 0; device_count <= device; device_count++) + { + // Check for requested device + result = deckLinkIterator->Next(&deckLink); + if (result != S_OK) + throw DecklinkError("No DeckLink PCI cards found."); + + if (device_count == device) + break; + } if (deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput) != S_OK) throw DecklinkError("DeckLink QueryInterface Failed."); @@ -62,7 +69,7 @@ void DecklinkReader::Open() throw(DecklinkError) throw DecklinkError("Failed to create a VideoConversionInstance(), used to convert YUV to RGB."); // Create Delegate & Pass in pointers to the output and converters - delegate = new DeckLinkCaptureDelegate(&sleepCond, m_deckLinkOutput, m_deckLinkConverter); + delegate = new DeckLinkInputDelegate(&sleepCond, m_deckLinkOutput, m_deckLinkConverter); deckLinkInput->SetCallback(delegate); diff --git a/src/DecklinkWriter.cpp b/src/DecklinkWriter.cpp new file mode 100644 index 00000000..b252597a --- /dev/null +++ b/src/DecklinkWriter.cpp @@ -0,0 +1,223 @@ +#include "../include/DecklinkWriter.h" + +using namespace openshot; + +DecklinkWriter::DecklinkWriter(int device, int video_mode, int pixel_format, int channels, int sample_depth) throw(DecklinkError) + : device(device), is_open(false), g_videoModeIndex(video_mode), g_audioChannels(channels), g_audioSampleDepth(sample_depth) +{ + // Init FileInfo struct (clear all values) + InitFileInfo(); + + // Init decklink variables + inputFlags = 0; + selectedDisplayMode = bmdModeNTSC; + pixelFormat = bmdFormat8BitYUV; + displayModeCount = 0; + exitStatus = 1; + foundDisplayMode = false; + pthread_mutex_init(&sleepMutex, NULL); + pthread_cond_init(&sleepCond, NULL); + + switch(pixel_format) + { + case 0: pixelFormat = bmdFormat8BitYUV; break; + case 1: pixelFormat = bmdFormat10BitYUV; break; + case 2: pixelFormat = bmdFormat10BitRGB; break; + case 3: pixelFormat = bmdFormat8BitARGB; break; + default: + throw DecklinkError("Pixel format is not valid (must be 0,1,2,3)."); + } +} + +// Open image file +void DecklinkWriter::Open() throw(DecklinkError) +{ + // Open reader if not already open + if (!is_open) + { + // Attempt to open blackmagic card + deckLinkIterator = CreateDeckLinkIteratorInstance(); + + if (!deckLinkIterator) + throw DecklinkError("This application requires the DeckLink drivers installed."); + + /* Connect to a DeckLink instance */ + for (int device_count = 0; device_count <= device; device_count++) + { + // Check for requested device + result = deckLinkIterator->Next(&deckLink); + if (result != S_OK) + throw DecklinkError("No DeckLink PCI cards found."); + + if (device_count == device) + break; + } + + if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&deckLinkOutput) != S_OK) + throw DecklinkError("DeckLink QueryInterface Failed."); + + // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output + result = deckLinkOutput->GetDisplayModeIterator(&displayModeIterator); + if (result != S_OK) + throw DecklinkError("Could not obtain the video output display mode iterator."); + + if (g_videoModeIndex < 0) + throw DecklinkError("No video mode specified."); + + // Loop through all available display modes, until a match is found (if any) + const char *displayModeName; + BMDTimeValue frameRateDuration, frameRateScale; + + while (displayModeIterator->Next(&displayMode) == S_OK) + { + if (g_videoModeIndex == displayModeCount) + { + BMDDisplayModeSupport result; + + foundDisplayMode = true; + displayMode->GetName(&displayModeName); + selectedDisplayMode = displayMode->GetDisplayMode(); + //deckLinkOutput->DoesSupportVideoMode(selectedDisplayMode, pixelFormat, bmdVideoOutputFlagDefault, &result, NULL); + + // Get framerate + displayMode->GetFrameRate(&frameRateDuration, &frameRateScale); + + //if (result == bmdDisplayModeNotSupported) + //{ + // cout << "The display mode does not support the selected pixel format." << endl; + // throw DecklinkError("The display mode does not support the selected pixel format."); + //} + + break; + } + displayModeCount++; + } + + if (!foundDisplayMode) + throw DecklinkError("Invalid video mode. No matching ones found."); + + // Calculate FPS + unsigned long m_framesPerSecond = (unsigned long)((frameRateScale + (frameRateDuration-1)) / frameRateDuration); + + // Create Delegate & Pass in pointers to the output and converters + delegate = new DeckLinkOutputDelegate(displayMode, deckLinkOutput); + + // Provide this class as a delegate to the audio and video output interfaces + deckLinkOutput->SetScheduledFrameCompletionCallback(delegate); + //deckLinkOutput->SetAudioCallback(delegate); + + // Check for video input + if (deckLinkOutput->EnableVideoOutput(displayMode->GetDisplayMode(), bmdVideoOutputFlagDefault) != S_OK) + throw DecklinkError("Failed to enable video output. Is another application using the card?"); + + // Check for audio input + //if (deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, g_audioSampleDepth, g_audioChannels, bmdAudioOutputStreamContinuous) != S_OK) + // throw DecklinkError("Failed to enable audio output. Is another application using the card?"); + + // Begin video preroll by scheduling a second of frames in hardware + tr1::shared_ptr f(new Frame(1, displayMode->GetWidth(), displayMode->GetHeight(), "Blue")); + f->AddColor(displayMode->GetWidth(), displayMode->GetHeight(), "Blue"); + + // Preroll 1 second of video + for (unsigned i = 0; i < 16; i++) + { + // Write 30 blank frames (for preroll) + delegate->WriteFrame(f); + delegate->ScheduleNextFrame(true); + } + + deckLinkOutput->StartScheduledPlayback(0, 100, 1.0); + //if (deckLinkOutput->BeginAudioPreroll() != S_OK) + // throw DecklinkError("Failed to begin audio preroll."); + + + // Update image properties + info.has_audio = true; + info.has_video = true; + info.vcodec = displayModeName; + info.width = displayMode->GetWidth(); + info.height = displayMode->GetHeight(); + info.file_size = info.width * info.height * sizeof(char) * 4; + info.pixel_ratio.num = 1; + info.pixel_ratio.den = 1; + info.duration = 60 * 60 * 24; // 24 hour duration... since we're capturing a live stream + info.fps.num = frameRateScale; + info.fps.den = frameRateDuration; + info.video_timebase.num = frameRateDuration; + info.video_timebase.den = frameRateScale; + info.video_length = round(info.duration * info.fps.ToDouble()); + + // Calculate the DAR (display aspect ratio) + Fraction size(info.width * info.pixel_ratio.num, info.height * info.pixel_ratio.den); + + // Reduce size fraction + size.Reduce(); + + // Set the ratio based on the reduced fraction + info.display_ratio.num = size.num; + info.display_ratio.den = size.den; + + // Mark as "open" + is_open = true; + } +} + +// Close device and video stream +void DecklinkWriter::Close() +{ + // Close all objects, if reader is 'open' + if (is_open) + { + // Stop the audio and video output streams immediately + deckLinkOutput->StopScheduledPlayback(0, NULL, 0); + deckLinkOutput->DisableAudioOutput(); + deckLinkOutput->DisableVideoOutput(); + + // Release DisplayMode + displayMode->Release(); + + if (displayModeIterator != NULL) + { + displayModeIterator->Release(); + displayModeIterator = NULL; + } + + if (deckLinkOutput != NULL) + { + deckLinkOutput->Release(); + deckLinkOutput = NULL; + } + + if (deckLink != NULL) + { + deckLink->Release(); + deckLink = NULL; + } + + if (deckLinkIterator != NULL) + deckLinkIterator->Release(); + + // Mark as "closed" + is_open = false; + } +} + +// This method is required for all derived classes of FileWriterBase. Write a Frame to the video file. +void DecklinkWriter::WriteFrame(tr1::shared_ptr frame) +{ + delegate->WriteFrame(frame); +} + +// This method is required for all derived classes of FileWriterBase. Write a block of frames from a reader. +void DecklinkWriter::WriteFrame(FileReaderBase* reader, int start, int length) +{ + // Loop through each frame (and encoded it) + for (int number = start; number <= length; number++) + { + // Get the frame + tr1::shared_ptr f = reader->GetFrame(number); + + // Encode frame + WriteFrame(f); + } +} diff --git a/src/Frame.cpp b/src/Frame.cpp index f1c63aa3..4715115b 100644 --- a/src/Frame.cpp +++ b/src/Frame.cpp @@ -400,9 +400,11 @@ int Frame::GetWidth() void Frame::TransparentColors(string color, double fuzz) { // Make this range of colors transparent - image->colorFuzz(fuzz * 65535 / 100.0); - image->transparent(Magick::Color(color)); - image->colorFuzz(0); + //image->colorFuzz(fuzz * 65535 / 100.0); + //image->transparent(Magick::Color(color)); + //image->colorFuzz(0); + image->negate(); + image->flip(); } // Save the frame image to the specified path. The image format is determined from the extension (i.e. image.PNG, image.JPEG) diff --git a/src/Main_Blackmagic.cpp b/src/Main_Blackmagic.cpp index 89479a99..6d7c4520 100644 --- a/src/Main_Blackmagic.cpp +++ b/src/Main_Blackmagic.cpp @@ -12,242 +12,40 @@ using namespace openshot; int main(int argc, char *argv[]) { - IDeckLink *deckLink; - IDeckLinkInput *deckLinkInput; - IDeckLinkDisplayModeIterator *displayModeIterator; - IDeckLinkOutput *m_deckLinkOutput; - IDeckLinkVideoConversion *m_deckLinkConverter; - pthread_mutex_t sleepMutex; - pthread_cond_t sleepCond; - IDeckLinkIterator *deckLinkIterator = CreateDeckLinkIteratorInstance(); - DeckLinkCaptureDelegate *delegate; - IDeckLinkDisplayMode *displayMode; - BMDVideoInputFlags inputFlags = 0; - BMDDisplayMode selectedDisplayMode = bmdModeNTSC; - BMDPixelFormat pixelFormat = bmdFormat8BitYUV; - int displayModeCount = 0; - int exitStatus = 1; - int ch; - bool foundDisplayMode = false; - HRESULT result; + // Image Reader + ImageReader r1("/home/jonathan/Pictures/moon.jpg"); + r1.Open(); + tr1::shared_ptr f1 = r1.GetFrame(1); + r1.Close(); - int g_videoModeIndex = -1; - int g_audioChannels = 2; - int g_audioSampleDepth = 16; - int g_maxFrames = 50; + ImageReader r2("/home/jonathan/Pictures/trees.jpg"); + r2.Open(); + tr1::shared_ptr f2 = r2.GetFrame(1); + r2.Close(); - pthread_mutex_init(&sleepMutex, NULL); - pthread_cond_init(&sleepCond, NULL); + DecklinkReader dr(1, 11, 0, 2, 16); + dr.Open(); - if (!deckLinkIterator) + DecklinkWriter w(0, 9, 3, 2, 16); + w.Open(); + + // Loop through reader + while (true) { - fprintf(stderr, "This application requires the DeckLink drivers installed.\n"); - goto bail; - } - - /* Connect to the first DeckLink instance */ - result = deckLinkIterator->Next(&deckLink); - if (result != S_OK) - { - fprintf(stderr, "No DeckLink PCI cards found.\n"); - goto bail; - } - - if (deckLink->QueryInterface(IID_IDeckLinkInput, (void**)&deckLinkInput) != S_OK) - goto bail; - - // Obtain an IDeckLinkDisplayModeIterator to enumerate the display modes supported on output - result = deckLinkInput->GetDisplayModeIterator(&displayModeIterator); - if (result != S_OK) - { - fprintf(stderr, "Could not obtain the video output display mode iterator - result = %08x\n", result); - goto bail; - } - - // Init deckLinkOutput (needed for color conversion) - if (deckLink->QueryInterface(IID_IDeckLinkOutput, (void**)&m_deckLinkOutput) != S_OK) - { - cout << "Failed to create a deckLinkOutput(), used to convert YUV to RGB." << endl; - m_deckLinkOutput = NULL; - } - - // Init the YUV to RGB conversion - if(!(m_deckLinkConverter = CreateVideoConversionInstance())) - { - cout << "Failed to create a VideoConversionInstance(), used to convert YUV to RGB." << endl; - m_deckLinkConverter = NULL; - } - - - // Create Delegate & Pass in pointers to the output and converters - delegate = new DeckLinkCaptureDelegate(&sleepCond, m_deckLinkOutput, m_deckLinkConverter); - deckLinkInput->SetCallback(delegate); - - - // Parse command line options - while ((ch = getopt(argc, argv, "?h3c:s:f:a:m:n:p:t:")) != -1) - { - switch (ch) + tr1::shared_ptr f = dr.GetFrame(0); + if (f) { - case 'm': - g_videoModeIndex = atoi(optarg); - break; - case 'c': - g_audioChannels = atoi(optarg); - if (g_audioChannels != 2 && - g_audioChannels != 8 && - g_audioChannels != 16) - { - fprintf(stderr, "Invalid argument: Audio Channels must be either 2, 8 or 16\n"); - goto bail; - } - break; - case 's': - g_audioSampleDepth = atoi(optarg); - if (g_audioSampleDepth != 16 && g_audioSampleDepth != 32) - { - fprintf(stderr, "Invalid argument: Audio Sample Depth must be either 16 bits or 32 bits\n"); - goto bail; - } - break; - case 'n': - g_maxFrames = atoi(optarg); - break; - case '3': - inputFlags |= bmdVideoInputDualStream3D; - break; - case 'p': - switch(atoi(optarg)) - { - case 0: pixelFormat = bmdFormat8BitYUV; break; - case 1: pixelFormat = bmdFormat10BitYUV; break; - case 2: pixelFormat = bmdFormat10BitRGB; break; - default: - fprintf(stderr, "Invalid argument: Pixel format %d is not valid", atoi(optarg)); - goto bail; - } - break; - case 't': - { - fprintf(stderr, "Invalid argument: Timecode format \"%s\" is invalid\n", optarg); - goto bail; - } - break; - //case '?': - //case 'h': - // usage(0); + //f->Display(); + w.WriteFrame(f); } } - if (g_videoModeIndex < 0) - { - fprintf(stderr, "No video mode specified\n"); - //usage(0); - } + // Sleep + sleep(4); - while (displayModeIterator->Next(&displayMode) == S_OK) - { - if (g_videoModeIndex == displayModeCount) - { - BMDDisplayModeSupport result; - const char *displayModeName; + // Close writer + //w.Close(); - foundDisplayMode = true; - displayMode->GetName(&displayModeName); - selectedDisplayMode = displayMode->GetDisplayMode(); - - deckLinkInput->DoesSupportVideoMode(selectedDisplayMode, pixelFormat, bmdVideoInputFlagDefault, &result, NULL); - - if (result == bmdDisplayModeNotSupported) - { - fprintf(stderr, "The display mode %s is not supported with the selected pixel format\n", displayModeName); - goto bail; - } - - if (inputFlags & bmdVideoInputDualStream3D) - { - if (!(displayMode->GetFlags() & bmdDisplayModeSupports3D)) - { - fprintf(stderr, "The display mode %s is not supported with 3D\n", displayModeName); - goto bail; - } - } - - break; - } - displayModeCount++; - displayMode->Release(); - } - - if (!foundDisplayMode) - { - fprintf(stderr, "Invalid mode %d specified\n", g_videoModeIndex); - goto bail; - } - - result = deckLinkInput->EnableVideoInput(selectedDisplayMode, pixelFormat, inputFlags); - if(result != S_OK) - { - fprintf(stderr, "Failed to enable video input. Is another application using the card?\n"); - goto bail; - } - - result = deckLinkInput->EnableAudioInput(bmdAudioSampleRate48kHz, g_audioSampleDepth, g_audioChannels); - if(result != S_OK) - { - goto bail; - } - - result = deckLinkInput->StartStreams(); - if(result != S_OK) - { - goto bail; - } - - // Test GetFrame() method - for (int x = 0; x < 10000; x++) - { - tr1::shared_ptr f = delegate->GetFrame(0); - //if (f) - // cout << "Found Frame!" << endl; - //else - // cout << "No Frame Found" << endl; - - usleep(20000); - } - - // All Okay. - exitStatus = 0; - - // Block main thread until signal occurs - pthread_mutex_lock(&sleepMutex); - pthread_cond_wait(&sleepCond, &sleepMutex); - pthread_mutex_unlock(&sleepMutex); - fprintf(stderr, "Stopping Capture\n"); - -bail: - - if (displayModeIterator != NULL) - { - displayModeIterator->Release(); - displayModeIterator = NULL; - } - - if (deckLinkInput != NULL) - { - deckLinkInput->Release(); - deckLinkInput = NULL; - } - - if (deckLink != NULL) - { - deckLink->Release(); - deckLink = NULL; - } - - if (deckLinkIterator != NULL) - deckLinkIterator->Release(); - - return exitStatus; + return 0; } diff --git a/src/openshot.i b/src/openshot.i index a789f0bb..e5737185 100644 --- a/src/openshot.i +++ b/src/openshot.i @@ -41,6 +41,7 @@ #ifdef USE_BLACKMAGIC %{ #include "../include/DecklinkReader.h" + #include "../include/DecklinkWriter.h" %} #endif @@ -51,6 +52,7 @@ %include "../include/Coordinate.h" #ifdef USE_BLACKMAGIC %include "../include/DecklinkReader.h" + %include "../include/DecklinkWriter.h" #endif %include "../include/DummyReader.h" %include "../include/Exceptions.h"