2013-02-10 02:19:40 -06:00
|
|
|
/* -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)
|
|
|
|
|
{
|
2013-02-12 01:28:48 -06:00
|
|
|
// reference to output device
|
2013-02-10 02:19:40 -06:00
|
|
|
deckLinkOutput = m_deckLinkOutput;
|
|
|
|
|
|
2013-02-12 01:28:48 -06:00
|
|
|
// init some variables
|
2013-02-10 02:19:40 -06:00
|
|
|
m_totalFramesScheduled = 0;
|
|
|
|
|
m_audioChannelCount = 2;
|
|
|
|
|
m_audioSampleRate = bmdAudioSampleRate48kHz;
|
|
|
|
|
m_audioSampleDepth = 16;
|
|
|
|
|
m_outputSignal = kOutputSignalDrop;
|
2013-02-12 01:28:48 -06:00
|
|
|
m_currentFrame = NULL;
|
2013-02-10 02:19:40 -06:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
{
|
2013-02-12 02:42:18 -06:00
|
|
|
//cout << "Scheduled Successfully!" << endl;
|
2013-02-10 02:19:40 -06:00
|
|
|
|
|
|
|
|
// 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 ()
|
|
|
|
|
{
|
2013-02-12 02:42:18 -06:00
|
|
|
//cout << "PLAYBACK HAS STOPPED!!!" << endl;
|
2013-02-10 02:19:40 -06:00
|
|
|
return S_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HRESULT DeckLinkOutputDelegate::RenderAudioSamples (bool preroll)
|
|
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
// // 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);
|
|
|
|
|
// }
|
2013-02-10 02:19:40 -06:00
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2013-02-12 01:28:48 -06:00
|
|
|
// Release the current frame (if any)
|
|
|
|
|
if (m_currentFrame)
|
|
|
|
|
{
|
|
|
|
|
m_currentFrame->Release();
|
|
|
|
|
m_currentFrame = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get the next frame off the queue
|
2013-02-12 02:42:18 -06:00
|
|
|
//cout << "Queue: get next (" << final_frames.size() - 1 << " remaining)" << endl;
|
2013-02-12 01:28:48 -06:00
|
|
|
m_currentFrame = final_frames.front();
|
2013-02-10 02:19:40 -06:00
|
|
|
final_frames.pop_front(); // remove this frame from the queue
|
|
|
|
|
}
|
2013-02-12 01:28:48 -06:00
|
|
|
else
|
|
|
|
|
cout << "Queue: empty on writer..." << endl;
|
|
|
|
|
|
|
|
|
|
// Schedule a frame to be displayed
|
|
|
|
|
if (m_currentFrame && deckLinkOutput->ScheduleVideoFrame(m_currentFrame, (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;
|
|
|
|
|
|
2013-02-10 02:19:40 -06:00
|
|
|
} // critical
|
2013-02-10 21:16:46 -06:00
|
|
|
|
|
|
|
|
|
2013-02-10 02:19:40 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DeckLinkOutputDelegate::WriteFrame(tr1::shared_ptr<openshot::Frame> 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);
|
2013-02-10 21:16:46 -06:00
|
|
|
#pragma omp parallel
|
2013-02-10 02:19:40 -06:00
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
#pragma omp single
|
2013-02-10 02:19:40 -06:00
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
// Temp frame counters (to keep the frames in order)
|
|
|
|
|
frameCount = 0;
|
|
|
|
|
|
2013-02-10 02:19:40 -06:00
|
|
|
// Loop through each queued image frame
|
|
|
|
|
while (!raw_video_frames.empty())
|
|
|
|
|
{
|
|
|
|
|
// Get front frame (from the queue)
|
|
|
|
|
tr1::shared_ptr<openshot::Frame> 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
|
2013-02-12 02:42:18 -06:00
|
|
|
usleep(1000 * 1);
|
2013-02-10 02:19:40 -06:00
|
|
|
}
|
|
|
|
|
|
2013-02-10 21:16:46 -06:00
|
|
|
// copy of frame count
|
|
|
|
|
unsigned long copy_frameCount(frameCount);
|
|
|
|
|
|
|
|
|
|
#pragma omp task firstprivate(m_rgbFrame, frame, copy_frameCount)
|
|
|
|
|
{
|
|
|
|
|
// *********** 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)
|
2013-02-10 02:19:40 -06:00
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
// Update buffer (which is already linked to the AVFrame: pFrameRGB)
|
2013-02-12 03:16:16 -06:00
|
|
|
castBytes[row] = 0; // alpha
|
|
|
|
|
castBytes[row+1] = pixel_packets[packet].red / 255;
|
|
|
|
|
castBytes[row+2] = pixel_packets[packet].green / 255;
|
|
|
|
|
castBytes[row+3] = pixel_packets[packet].blue / 255;
|
2013-02-10 21:16:46 -06:00
|
|
|
}
|
2013-02-10 02:19:40 -06:00
|
|
|
|
2013-02-10 21:16:46 -06:00
|
|
|
#pragma omp critical (blackmagic_output_queue)
|
|
|
|
|
{
|
2013-02-12 03:16:16 -06:00
|
|
|
//if (20 == frame->number)
|
|
|
|
|
// frame->Display();
|
2013-02-10 21:16:46 -06:00
|
|
|
// Add processed frame to cache (to be recalled in order after the thread pool is done)
|
|
|
|
|
temp_cache[copy_frameCount] = m_rgbFrame;
|
|
|
|
|
}
|
2013-02-10 02:19:40 -06:00
|
|
|
|
2013-02-10 21:16:46 -06:00
|
|
|
// Add processed RGB frame to final_frames
|
|
|
|
|
//final_frames.push_back(m_rgbFrame);
|
|
|
|
|
} // end task
|
2013-02-10 02:19:40 -06:00
|
|
|
|
2013-02-10 21:16:46 -06:00
|
|
|
// Increment frame count
|
|
|
|
|
frameCount++;
|
2013-02-10 02:19:40 -06:00
|
|
|
|
|
|
|
|
} // end while
|
|
|
|
|
} // omp single
|
|
|
|
|
} // omp parallel
|
|
|
|
|
|
2013-02-10 21:16:46 -06:00
|
|
|
|
|
|
|
|
// Add frames to final queue (in order)
|
|
|
|
|
for (int z = 0; z < frameCount; z++)
|
2013-02-10 02:19:40 -06:00
|
|
|
{
|
2013-02-10 21:16:46 -06:00
|
|
|
IDeckLinkMutableVideoFrame *m_rgbFrame = temp_cache[z];
|
|
|
|
|
// Add to final queue
|
|
|
|
|
final_frames.push_back(m_rgbFrame);
|
2013-02-10 02:19:40 -06:00
|
|
|
}
|
2013-02-10 21:16:46 -06:00
|
|
|
|
|
|
|
|
// Clear temp cache
|
|
|
|
|
temp_cache.clear();
|
|
|
|
|
|
|
|
|
|
|
2013-02-12 02:42:18 -06:00
|
|
|
//cout << "final_frames.size(): " << final_frames.size() << ", raw_video_frames.size(): " << raw_video_frames.size() << endl;
|
2013-02-10 21:16:46 -06:00
|
|
|
if (final_frames.size() >= m_framesPerSecond && m_totalFramesScheduled == 0)
|
|
|
|
|
{
|
|
|
|
|
cout << "Prerolling!" << endl;
|
|
|
|
|
|
|
|
|
|
for (int x = 0; x < final_frames.size(); x++)
|
|
|
|
|
ScheduleNextFrame(true);
|
|
|
|
|
|
|
|
|
|
cout << "Starting scheduled playback!" << endl;
|
|
|
|
|
|
|
|
|
|
// Start playback when enough frames have been processed
|
|
|
|
|
deckLinkOutput->StartScheduledPlayback(0, 100, 1.0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Be sure we don't have too many extra frames
|
|
|
|
|
while (final_frames.size() > (m_framesPerSecond + 15))
|
|
|
|
|
{
|
|
|
|
|
cout << "Too many, so remove some..." << endl;
|
|
|
|
|
// Remove oldest frame
|
|
|
|
|
final_frames.front()->Release();
|
|
|
|
|
final_frames.pop_front();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-10 02:19:40 -06:00
|
|
|
|
|
|
|
|
} // if
|
|
|
|
|
|
|
|
|
|
}
|