Files
UltraGrid/src/video_display/bluefish444.cpp
Martin Piatka 2255a71d67 capabilities: Make video and audio devices consistent
The .id field previously contained the module name for audio devices,
while it only contained options for video devices. To make things more
consistent there are now two fields: "module" which contains the module
name and "device" which contains only the parameters.
2021-03-12 13:48:03 +01:00

1048 lines
38 KiB
C++

/**
* @file video_display/bluefish444.cpp
* @author Martin Pulec <pulec@cesnet.cz>
*/
/*
* Copyright (c) 2013-2020 CESNET, z. s. p. o.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config_unix.h"
#include "config_win32.h"
#endif
#include "video_display.h"
#include <iomanip>
#include <iostream>
#include <queue>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "bluefish444_common.h"
#include "audio/audio.h"
#include "debug.h"
#include "host.h"
#include "lib_common.h"
#include "tv.h"
#include "utils/ring_buffer.h"
#include "video.h"
#include "video_display.h"
#define BLUEFISH444_MAGIC 0x15b75db8
#define BUFFER_COUNT 4
#define MAX_BLUE_OUT_CHANNELS 4
using namespace std;
static void InitOutputChannel(BLUEVELVETC_HANDLE pSDK, ULONG DefaultOutputChannel,
ULONG UpdateFormat, ULONG MemoryFormat, ULONG VideoEngine);
static void RouteChannel(BLUEVELVETC_HANDLE pSDK, ULONG Source, ULONG Destination,
ULONG LinkType);
struct av_buffer
{
av_buffer(int TilesCount, int GoldenSize) :
pVideoBuffer(TilesCount), BufferId(0), mGoldenSize(GoldenSize)
{
for(int i = 0; i < TilesCount; ++i) {
pVideoBuffer[i] = (unsigned int *) bfAlloc(GoldenSize);
}
}
virtual ~av_buffer() {
for(unsigned int i = 0; i < pVideoBuffer.size(); ++i) {
bfFree(mGoldenSize, pVideoBuffer[i]);
}
}
vector<unsigned int *> pVideoBuffer;
unsigned int BufferId;
int mGoldenSize;
};
struct display_bluefish444_state {
public:
display_bluefish444_state(unsigned int flags, int deviceId = 1)
noexcept(false);
virtual ~display_bluefish444_state() noexcept;
struct video_frame *getf() noexcept;
void putf(struct video_frame *) noexcept(false);
void reconfigure(struct video_desc) noexcept(false);
void cleanup() noexcept;
static void *playback_thread(void *arg) noexcept;
void *playback_loop() noexcept;
#ifdef HAVE_BLUE_AUDIO
/// AUDIO
void reconfigure_audio(int quant_samples, int channels,
int sample_rate) noexcept(false);
void put_audio_frame(struct audio_frame *) noexcept;
#endif
private:
uint32_t m_magic;
struct video_frame *m_frame;
int m_deviceId;
BLUEVELVETC_HANDLE m_pSDK[MAX_BLUE_OUT_CHANNELS];
int m_AttachedDevicesCount;
BLUE_S32 m_CardType;
int m_CardFirmware;
int m_TileCount;
int m_Offset[MAX_BLUE_OUT_CHANNELS];
ULONG m_InvalidVideoModeFlag;
ULONG m_CurrentVideoMode;
BLUE_U32 m_GoldenSize;
unsigned long int m_LastFieldCount;
pthread_mutex_t m_lock;
pthread_cond_t m_WorkerCv;
pthread_cond_t m_BossCv;
pthread_t m_thread;
queue<struct av_buffer *> m_ReadyFrameQueue;
bool m_WorkerWaiting;
queue<struct av_buffer *> m_FreeFrameQueue;
struct av_buffer *m_pPlayingBuffer;
// this is temporary storage between getf/putf calls
av_buffer *m_pTmpFrame;
#ifdef HAVE_BLUE_AUDIO
struct audio_desc m_AudioDesc;
hanc_stream_info_struct m_HancInfo;
pthread_spinlock_t m_AudioSpinLock;
struct ring_buffer *m_AudioRingBuffer;
#endif
bool m_PlayAudio;
};
display_bluefish444_state::display_bluefish444_state(unsigned int flags,
int deviceId) noexcept(false) :
m_magic(BLUEFISH444_MAGIC),
m_frame(NULL),
m_deviceId(deviceId),
m_AttachedDevicesCount(0),
m_GoldenSize(0),
m_LastFieldCount(0),
m_pPlayingBuffer(NULL),
#ifdef HAVE_BLUE_AUDIO
m_AudioRingBuffer(ring_buffer_init(48000*4*16*8)),
#endif
m_PlayAudio(false)
{
BLUE_S32 iDevices = 0;
BLUE_U32 val32;
if(flags) {
#ifdef HAVE_BLUE_AUDIO
m_PlayAudio = true;
#else
throw runtime_error("Audio support missing in this build");
#endif
}
pthread_mutex_init(&m_lock, NULL);
pthread_cond_init(&m_WorkerCv, NULL);
pthread_cond_init(&m_BossCv, NULL);
BLUEVELVETC_HANDLE pSDK = bfcFactory();
bfcEnumerate(pSDK, &iDevices);
if(iDevices < 1) {
bfcDestroy(pSDK);
throw runtime_error("No Bluefish card detected");
}
bfcQueryCardType(pSDK, &m_CardType, m_deviceId);
bfcQueryCardProperty32(pSDK, INVALID_VIDEO_MODE_FLAG, &val32);
m_InvalidVideoModeFlag = val32;
if(BLUE_FAIL(bfcAttach(pSDK, m_deviceId))) {
bfcDestroy(pSDK);
throw runtime_error("Unable to attach card");
}
bfcQueryCardProperty32(pSDK, EPOCH_GET_PRODUCT_ID, &val32);
m_CardFirmware = val32;
m_deviceId = deviceId;
bfcDetach(pSDK);
bfcDestroy(pSDK);
#ifdef HAVE_BLUE_AUDIO
memset(&m_AudioDesc, 0, sizeof(m_AudioDesc));
memset(&m_HancInfo, 0, sizeof(m_HancInfo));
for(int i = 0; i < 4; ++i) {
m_HancInfo.AudioDBNArray[i] = -1;
}
m_HancInfo.hanc_data_ptr = (unsigned int *) bfAlloc(MAX_HANC_SIZE);
pthread_spin_init(&m_AudioSpinLock, 0);
#endif
pthread_create(&m_thread, NULL, playback_thread, this);
}
display_bluefish444_state::~display_bluefish444_state() noexcept
{
assert(m_magic == BLUEFISH444_MAGIC);
// Kill thread
pthread_mutex_lock(&m_lock);
m_ReadyFrameQueue.push(NULL);
pthread_cond_signal(&m_WorkerCv);
pthread_mutex_unlock(&m_lock);
unsigned long int FieldCount;
//cards must be genlocked; only then all for output channels are completely in synch
bfcWaitVideoOutputSync(m_pSDK[0], UPD_FMT_FRAME, &FieldCount);
pthread_join(m_thread, NULL);
cleanup();
pthread_mutex_destroy(&m_lock);
pthread_cond_destroy(&m_WorkerCv);
pthread_cond_destroy(&m_BossCv);
#ifdef HAVE_BLUE_AUDIO
ring_buffer_destroy(m_AudioRingBuffer);
bfFree(MAX_HANC_SIZE, m_HancInfo.hanc_data_ptr);
pthread_spin_destroy(&m_AudioSpinLock);
#endif
}
void *display_bluefish444_state::playback_thread(void *arg) noexcept
{
display_bluefish444_state *s =
(display_bluefish444_state *) arg;
return s->playback_loop();
}
void *display_bluefish444_state::playback_loop() noexcept
{
uint32_t FrameCount = 0;
struct timeval t0;
#ifdef HAVE_BLUE_AUDIO
uint32_t EmbAudioFlag = blue_enable_hanc_timestamp_pkt | blue_emb_audio_enable; // enable timecode
#endif
gettimeofday(&t0, NULL);
#ifdef WIN32
OVERLAPPED Overlapped[MAX_BLUE_OUT_CHANNELS];
for (int i = 0;i < MAX_BLUE_OUT_CHANNELS; ++ i) {
Overlapped[i].hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
}
#endif
while(1) {
struct av_buffer *frame;
pthread_mutex_lock(&m_lock);
while(m_ReadyFrameQueue.empty()) {
m_WorkerWaiting = true;
pthread_cond_wait(&m_WorkerCv, &m_lock);
m_WorkerWaiting = false;
}
frame = m_ReadyFrameQueue.front();
m_ReadyFrameQueue.pop();
pthread_mutex_unlock(&m_lock);
if(frame == NULL) {
return NULL;
}
unsigned long int FieldCount = 0;
// for more than one input, cards must be genlocked
bfcWaitVideoOutputSync(m_pSDK[0], UPD_FMT_FRAME, &FieldCount);
if(m_pPlayingBuffer) {
pthread_mutex_lock(&m_lock);
m_FreeFrameQueue.push(m_pPlayingBuffer);
pthread_cond_signal(&m_BossCv);
pthread_mutex_unlock(&m_lock);
}
bool WaitResult = true;
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
unsigned char *videoBuffer;
#ifdef WIN32
OVERLAPPED *OverlapCh = &Overlapped[i];
#endif
if(m_TileCount == m_AttachedDevicesCount) {
videoBuffer = (unsigned char *) frame->pVideoBuffer[i];
} else { // untiled 4K
videoBuffer = (unsigned char *) frame->pVideoBuffer[0];
}
#ifdef WIN32
int err = bfcSystemBufferWriteAsync(
#else
int err = bfcSystemBufferWrite(
#endif
m_pSDK[i], videoBuffer + m_Offset[i], m_GoldenSize,
#ifdef WIN32
OverlapCh,
#endif
(m_PlayAudio && i == 0 ?
BlueImage_HANC_DMABuffer(frame->BufferId, BLUE_DATA_IMAGE) :
BlueImage_DMABuffer(frame->BufferId, BLUE_DATA_IMAGE)), 0
);
if(!BLUE_OK(err)) {
cerr << "Write failed (Channel " << (char) ('A' + i) << ")" << endl;
WaitResult = false;
}
}
#ifdef WIN32
if(WaitResult) {
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
DWORD BytesReturned = 0;
GetOverlappedResult(bfcGetHandle(m_pSDK[i]), &Overlapped[i], &BytesReturned, TRUE);
ResetEvent(Overlapped[i].hEvent);
}
}
#else
UNUSED(WaitResult);
#endif
#ifdef HAVE_BLUE_AUDIO
if(m_PlayAudio) {
m_HancInfo.time_code = 0LL; //RP188 VITC time code
m_HancInfo.rp188_ltc_time_code = 0LL; //RP188 LTC time code
m_HancInfo.ltc_time_code = 0LL; //external LTC time code
m_HancInfo.sd_vitc_time_code = 0LL; //this field is only valid for SD video signal
m_HancInfo.video_mode = m_CurrentVideoMode;
uint32_t NrSamples = 0;
pthread_spin_lock(&m_AudioSpinLock);
uint32_t NumberAudioChannels = m_AudioDesc.ch_count;
char audio_data[2002*4*16];
unsigned int nSampleType;
ULONG EmbAudioProp = 0;
if(NumberAudioChannels > 0) {
NrSamples = GetNumberOfAudioSamplesPerFrame(m_CurrentVideoMode,
FrameCount);
switch(m_AudioDesc.bps) {
case 2:
nSampleType = AUDIO_CHANNEL_16BIT; // AUDIO_CHANNEL_LITTLEENDIAN
break;
case 3:
nSampleType = AUDIO_CHANNEL_24BIT;
break;
default:
fprintf(stderr, "[Blue] Unhandled audio sample type!\n");
abort();
}
if(NumberAudioChannels > 0)
EmbAudioProp |= (blue_emb_audio_group1_enable);
if(NumberAudioChannels > 4)
EmbAudioProp |= (blue_emb_audio_group2_enable);
if(NumberAudioChannels > 8)
EmbAudioProp |= (blue_emb_audio_group3_enable);
if(NumberAudioChannels > 12)
EmbAudioProp |= (blue_emb_audio_group4_enable);
int bytes = ring_buffer_read(m_AudioRingBuffer, audio_data, NrSamples *
m_AudioDesc.bps *m_AudioDesc.ch_count);
NrSamples = bytes / m_AudioDesc.bps / m_AudioDesc.ch_count;
}
pthread_spin_unlock(&m_AudioSpinLock);
if(NrSamples > 0) {
bfcEncodeHancFrameEx(m_pSDK[0], m_CardType, &m_HancInfo, audio_data,
m_AudioDesc.ch_count, NrSamples,
nSampleType, EmbAudioProp | EmbAudioFlag);
//wait for both DMA transfers to be finished
//GetOverlappedResult(pSDK->m_hDevice, &OverlapChA, &BytesReturnedChA, TRUE);
//ResetEvent(OverlapChA.hEvent);
//now we can DMA the HANC frame
#ifdef WIN32
bfcSystemBufferWriteAsync(m_pSDK[0], (unsigned char *) m_HancInfo.hanc_data_ptr,
MAX_HANC_SIZE,
NULL, BlueImage_HANC_DMABuffer(frame->BufferId, BLUE_DATA_HANC));
#else
bfcSystemBufferWrite(m_pSDK[0], (unsigned char *) m_HancInfo.hanc_data_ptr,
MAX_HANC_SIZE,
BlueImage_HANC_DMABuffer(frame->BufferId, BLUE_DATA_HANC), 0);
#endif
}
}
#endif
//tell the card to playback the frames on the next interrupt
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
bfcRenderBufferUpdate(m_pSDK[i],
(m_PlayAudio && i == 0 ?
BlueBuffer_Image_HANC(frame->BufferId) :
BlueBuffer_Image(frame->BufferId))
);
}
if(FieldCount != m_LastFieldCount + 2) {
cerr << "[Blue444 disp] " << "Dropped " <<
((FieldCount - m_LastFieldCount + 2)/2) <<
" frames." << endl;
}
m_LastFieldCount = FieldCount;
FrameCount++;
struct timeval t;
gettimeofday(&t, NULL);
double seconds = tv_diff(t, t0);
if (seconds >= 5) {
float fps = FrameCount / seconds;
log_msg(LOG_LEVEL_INFO, "[Blue disp] %d frames in %g seconds = %g FPS\n",
FrameCount, seconds, fps);
t0 = t;
FrameCount = 0;
}
m_pPlayingBuffer = frame;
}
#ifdef WIN32
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
CloseHandle(Overlapped[i].hEvent);
}
#endif
return NULL;
}
void display_bluefish444_state::cleanup() noexcept
{
//turn on black generator (unless we want to keep displaying the last rendered frame)
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
uint32_t value;
value = ENUM_BLACKGENERATOR_ON;
bfcSetCardProperty32(m_pSDK[i], VIDEO_BLACKGENERATOR, value);
bfcVideoPlaybackStop(m_pSDK[i], 1, 1);
}
while(!m_FreeFrameQueue.empty()) {
struct av_buffer *frame = m_FreeFrameQueue.front();
m_FreeFrameQueue.pop();
delete frame;
}
if(m_pPlayingBuffer) {
delete m_pPlayingBuffer;
m_pPlayingBuffer = NULL;
}
vf_free(m_frame);
m_frame = NULL;
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
bfcDetach(m_pSDK[i]);
bfcDestroy(m_pSDK[i]);
}
m_AttachedDevicesCount = 0;
}
void display_bluefish444_state::reconfigure(struct video_desc desc)
noexcept(false)
{
ULONG VideoMode;
BLUE_U32 val32;
bool is4K;
bool isTiled4K;
int tile_width, tile_height;
pthread_mutex_lock(&m_lock);
while(!m_ReadyFrameQueue.empty() ||
!m_WorkerWaiting)
pthread_cond_wait(&m_BossCv, &m_lock);
pthread_mutex_unlock(&m_lock);
cleanup();
tile_width = desc.width;
tile_height = desc.height;
// 4K
if((desc.width == 4096 || desc.width == 3840) && desc.height == 2160) {
is4K = true;
isTiled4K = false;
tile_width /= 2;
tile_height /= 2;
} else if(desc.tile_count == 4 &&
(desc.width == 2048 || desc.width == 1920) &&
desc.height == 1080) {
// tiled 4K
is4K = true;
isTiled4K = true;
} else {
is4K = false;
isTiled4K = false;
}
m_TileCount = desc.tile_count;
if(is4K) {
// we need a SuperNova QuadOut card
if(m_CardType != CRD_BLUE_SUPER_NOVA) {
throw runtime_error("We need a SuperNova card for 4K mode! "
"Let us know if you need a different combination "
"(eg. 2x Bluefish card");
}
if(m_CardFirmware != ORAC_4SDIOUTPUT_FIRMWARE_PRODUCTID)
{
throw runtime_error("Wrong firmware; need 4 output channels (QuadOut)");
}
m_AttachedDevicesCount = 4;
} else {
m_AttachedDevicesCount = 1;
}
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
m_pSDK[i] = bfcFactory();
//Attach the SDK object to a specific card, in this case card 1
if(BLUE_FAIL(bfcAttach(m_pSDK[i], m_deviceId)))
{
throw runtime_error("Error on device attach");
}
}
VideoMode = m_InvalidVideoModeFlag;
for(int i = 0; i < bluefish_frame_modes_count; ++i) {
if((int) bluefish_frame_modes[i].width == tile_width &&
(int) bluefish_frame_modes[i].height == tile_height &&
fabs(bluefish_frame_modes[i].fps - desc.fps) < 0.01 &&
bluefish_frame_modes[i].interlacing == desc.interlacing)
{
VideoMode =
bluefish_frame_modes[i].mode;
}
}
if(VideoMode == m_InvalidVideoModeFlag) {
ostringstream msg_ss;
msg_ss << "Unable to find appropriate video format for " <<
tile_width << "x" << tile_height <<
get_interlacing_suffix(desc.interlacing) <<
" @" << fixed << setprecision(2) << desc.fps;
throw runtime_error(msg_ss.str());
}
m_CurrentVideoMode = VideoMode;
ULONG UpdateFormat = UPD_FMT_FRAME;
ULONG MemoryFormat;
switch(desc.color_spec) {
case UYVY:
MemoryFormat = MEM_FMT_2VUY;
break;
case v210:
MemoryFormat = MEM_FMT_V210;
break;
case RGBA:
MemoryFormat = MEM_FMT_RGBA;
break;
case DPX10:
MemoryFormat = MEM_FMT_CINEON_LITTLE_ENDIAN;
break;
default:
throw logic_error("Unknown video format!!!");
}
ULONG VideoEngine = VIDEO_ENGINE_FRAMESTORE;
ULONG OutputChannels[4] = {
BLUE_VIDEO_OUTPUT_CHANNEL_A,
BLUE_VIDEO_OUTPUT_CHANNEL_B,
BLUE_VIDEO_OUTPUT_CHANNEL_C,
BLUE_VIDEO_OUTPUT_CHANNEL_D
};
ULONG Sources[4] = { EPOCH_SRC_OUTPUT_MEM_INTERFACE_CHA,
EPOCH_SRC_OUTPUT_MEM_INTERFACE_CHB,
EPOCH_SRC_OUTPUT_MEM_INTERFACE_CHC,
EPOCH_SRC_OUTPUT_MEM_INTERFACE_CHD
};
ULONG Destinations[4] = { EPOCH_DEST_SDI_OUTPUT_A,
EPOCH_DEST_SDI_OUTPUT_B,
EPOCH_DEST_SDI_OUTPUT_C,
EPOCH_DEST_SDI_OUTPUT_D
};
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
InitOutputChannel(m_pSDK[i], OutputChannels[i],
UpdateFormat, MemoryFormat, VideoEngine);
RouteChannel(m_pSDK[i], Sources[i],
Destinations[i],
BLUE_CONNECTOR_PROP_SINGLE_LINK);
}
if(m_PlayAudio) {
//route the same audio to AES as well (AES only supports 8 channels, though)
val32 = EPOCH_SET_ROUTING(EPOCH_SRC_OUTPUT_MEM_INTERFACE_CHA,
EPOCH_DEST_AES_ANALOG_AUDIO_OUTPUT, BLUE_CONNECTOR_PROP_SINGLE_LINK);
bfcSetCardProperty32(m_pSDK[0], MR2_ROUTING, val32);
val32 = blue_emb_audio_group1_enable | blue_emb_audio_enable | blue_enable_hanc_timestamp_pkt;
bfcSetCardProperty32(m_pSDK[0], EMBEDDED_AUDIO_OUTPUT, val32);
}
//Set the required video mode
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
val32 = VideoMode;
bfcSetCardProperty32(m_pSDK[i], VIDEO_MODE, val32);
bfcQueryCardProperty32(m_pSDK[i], VIDEO_MODE, &val32);
if(val32 != VideoMode)
{
throw logic_error("Can't set video mode; FIFO running already?");
}
}
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
val32 = vc_get_linesize(tile_width, desc.color_spec);
bfcSetCardProperty32(m_pSDK[i], VIDEO_IMAGE_WIDTH, val32);
val32 = tile_height;
bfcSetCardProperty32(m_pSDK[i], VIDEO_IMAGE_HEIGHT, val32);
val32 = vc_get_linesize(desc.width, desc.color_spec);
bfcSetCardProperty32(m_pSDK[i], VIDEO_IMAGE_PITCH, val32);
}
BLUE_U32 Width, Height, BytesPerLine, BytesPerFrame, GoldenSize;
bfcGetVideoInfo(VideoMode, UpdateFormat, MemoryFormat, &Width, &Height, &BytesPerLine, &BytesPerFrame, &GoldenSize);
ULONG FrameSize = GoldenSize;
if(is4K && !isTiled4K) {
BytesPerFrame = vc_get_linesize(desc.width, desc.color_spec) *
desc.height;
// 4 * GoldenSize should be AFAIK correct but it keeps failing during
// DMA transfer of last segment of 4K video
FrameSize = 5 * GoldenSize;
}
cout << "Video Golden: " << GoldenSize << endl;
cout << "Video Pixels per line: " << Width << endl;
cout << "Video lines: " << Height << endl;
cout << "Video Bytes per frame: " << BytesPerFrame << endl;
cout << "Video Bytes per line: " << BytesPerLine << endl;
for(int i = 0; i < BUFFER_COUNT; ++i) {
av_buffer *new_buffer = new av_buffer(desc.tile_count, FrameSize);
m_FreeFrameQueue.push(new_buffer);
}
m_frame = vf_alloc_desc(desc);
for(unsigned int i = 0; i < desc.tile_count; ++i) {
m_frame->tiles[i].data_len = BytesPerFrame;
}
if(!is4K || isTiled4K) {
m_GoldenSize = GoldenSize;
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
m_Offset[i] = 0;
}
} else {
m_GoldenSize = vc_get_linesize(desc.width, desc.color_spec) * tile_height;
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
int x = i % 2;
int y = i / 2;
m_Offset[i] = y * m_GoldenSize + x *
vc_get_linesize(tile_width, desc.color_spec);
}
}
for(int i = 0; i < m_AttachedDevicesCount; ++i) {
bfcVideoPlaybackStart(m_pSDK[i], 0, 0);
}
}
struct video_frame *display_bluefish444_state::getf() noexcept
{
pthread_mutex_lock(&m_lock);
while(m_FreeFrameQueue.empty()) {
pthread_cond_wait(&m_BossCv, &m_lock);
}
m_pTmpFrame = m_FreeFrameQueue.front();
m_FreeFrameQueue.pop();
for(int i = 0; i < m_TileCount; ++i) {
m_frame->tiles[i].data = (char *) m_pTmpFrame->pVideoBuffer[i];
}
pthread_mutex_unlock(&m_lock);
return m_frame;
}
void display_bluefish444_state::putf(struct video_frame *frame) noexcept(false)
{
if (!frame)
return;
if (frame->tiles[0].data !=
(char *) m_pTmpFrame->pVideoBuffer[0]) {
throw invalid_argument("Invalid frame supplied!");
}
pthread_mutex_lock(&m_lock);
for(int i = 0; i < m_TileCount; ++i) {
m_pTmpFrame->pVideoBuffer[i] = (unsigned int *)(void *) m_frame->tiles[i].data;
}
m_ReadyFrameQueue.push(m_pTmpFrame);
pthread_cond_signal(&m_WorkerCv);
pthread_mutex_unlock(&m_lock);
}
/*
* Utility functions - from SDK
*/
static void InitOutputChannel(BLUEVELVETC_HANDLE pSDK, ULONG DefaultOutputChannel, ULONG UpdateFormat,
ULONG MemoryFormat, ULONG VideoEngine)
{
uint32_t val32;
//MOST IMPORTANT: as the first step set the channel that we want to work with
val32 = DefaultOutputChannel;
bfcSetCardProperty32(pSDK, DEFAULT_VIDEO_OUTPUT_CHANNEL, val32);
//make sure the FIFO hasn't been left running (e.g. application crash before), otherwise we can't change card properties
bfcVideoPlaybackStop(pSDK, 1, 1);
val32 = UpdateFormat;
bfcSetCardProperty32(pSDK, VIDEO_UPDATE_TYPE, val32);
val32 = MemoryFormat;
bfcSetCardProperty32(pSDK, VIDEO_MEMORY_FORMAT, val32);
//Only set the Video Engine after setting up the required video mode, update type and memory format
val32 = VideoEngine;
bfcSetCardProperty32(pSDK, VIDEO_OUTPUT_ENGINE, val32);
val32 = ENUM_BLACKGENERATOR_OFF;
bfcSetCardProperty32(pSDK, VIDEO_BLACKGENERATOR, val32);
}
static void RouteChannel(BLUEVELVETC_HANDLE pSDK, ULONG Source, ULONG Destination, ULONG LinkType)
{
uint32_t val32;
val32 = EPOCH_SET_ROUTING(Source, Destination, LinkType);
bfcSetCardProperty32(pSDK, MR2_ROUTING, val32);
}
/*
* AUDIO
*/
#ifdef HAVE_BLUE_AUDIO
void display_bluefish444_state::reconfigure_audio(int quant_samples, int channels,
int sample_rate) noexcept(false)
{
if(quant_samples <= 0 || channels <= 0 || sample_rate <= 0) {
throw logic_error("Wrong audio attributes");
}
if(sample_rate != 48000) {
throw runtime_error("Audio: only 48 kHz audio supported");
}
if(channels > 16) {
throw runtime_error("Audio: more than 16 channels not supported");
}
// TODO: fix if we want more this needs some property reset
if(channels > 4) {
throw runtime_error("Audio: more than 4 channels not supported");
}
if(quant_samples != 16 && quant_samples != 24) {
throw runtime_error("Audio: only 16 or 24 bits per second supported");
}
pthread_spin_lock(&m_AudioSpinLock);
m_AudioDesc.bps = quant_samples / 8;
m_AudioDesc.ch_count = channels;
m_AudioDesc.sample_rate = sample_rate;
ring_buffer_flush(m_AudioRingBuffer);
pthread_spin_unlock(&m_AudioSpinLock);
}
void display_bluefish444_state::put_audio_frame(struct audio_frame *frame) noexcept
{
if(!m_PlayAudio)
return;
ring_buffer_write(m_AudioRingBuffer, frame->data, frame->data_len);
}
#endif // defined HAVE_BLUE_AUDIO
/*
* API
*/
static void show_help(void);
static void show_help(void)
{
BLUE_S32 iDevices;
BLUEVELVETC_HANDLE pSDK = bfcFactory();
bfcEnumerate(pSDK, &iDevices);
bfcDestroy(pSDK);
cout << "bluefish444 (output) options:" << endl
<< "\tbluefish444[:device=<device_id>]" << endl
<< "\t\tID of the Bluefish device (if more present)" << endl
<< "\t\t" << iDevices << " Bluefish devices found in this system" << endl;
if(iDevices == 1) {
cout << " (default)" << endl;
} else if(iDevices > 1) {
cout << ", valid indices [1," << iDevices << "]" << endl;
cout << "default is 1" << endl;
}
}
static void display_bluefish444_probe(struct device_info **available_cards, int *count, void (**deleter)(void *))
{
UNUSED(deleter);
BLUE_S32 iDevices;
BLUEVELVETC_HANDLE pSDK = bfcFactory();
bfcEnumerate(pSDK, &iDevices);
bfcDestroy(pSDK);
*available_cards = (struct device_info *) calloc(iDevices, sizeof(struct device_info));
*count = iDevices;
for (int i = 0; i < iDevices; i++) {
sprintf((*available_cards)[i].dev, ":device=%d", iDevices);
sprintf((*available_cards)[i].name, "Bluefish444 card #%d", iDevices);
}
}
static void *display_bluefish444_init(struct module *parent, const char *fmt, unsigned int flags)
{
UNUSED(parent);
int deviceId = 1;
if(fmt){
if(strcmp(fmt, "help") == 0) {
show_help();
return NULL;
} else if(strncasecmp(fmt, "device=", strlen("device=")) == 0) {
deviceId = atoi(fmt + strlen("device="));
} else {
cerr << "[Blue444 disp] Unknown parameter: " << fmt << endl;
return NULL;
}
}
void *state = NULL;
try {
state = new display_bluefish444_state(flags, deviceId);
} catch(runtime_error &e) {
cerr << "[Blue444 disp] " << e.what() << endl;
}
return state;
}
static struct video_frame *
display_bluefish444_getf(void *state)
{
display_bluefish444_state *s =
(display_bluefish444_state *) state;
struct video_frame *frame = NULL;
try {
frame = s->getf();
} catch(runtime_error &e) {
cerr << "[Blue444 disp] " << e.what() << endl;
}
return frame;
}
static int display_bluefish444_putf(void *state, struct video_frame *frame, int nonblock)
{
UNUSED(nonblock);
display_bluefish444_state *s =
(display_bluefish444_state *) state;
int ret;
try {
s->putf(frame);
ret = 0;
} catch(runtime_error &e) {
cerr << "[Blue444 disp] " << e.what() << endl;
ret = -1;
}
return ret;
}
static int
display_bluefish444_reconfigure(void *state, struct video_desc desc)
{
display_bluefish444_state *s =
(display_bluefish444_state *) state;
int ret;
try {
s->reconfigure(desc);
ret = TRUE;
} catch(runtime_error &e) {
cerr << "[Blue444 disp] " << e.what() << endl;
ret = FALSE;
}
return ret;
}
static void display_bluefish444_run(void *state)
{
UNUSED(state);
}
static void display_bluefish444_done(void *state)
{
display_bluefish444_state *s =
(display_bluefish444_state *) state;
delete s;
}
static int display_bluefish444_get_property(void *state, int property, void *val, size_t *len)
{
UNUSED(state);
codec_t codecs[] = { UYVY };
interlacing_t supported_il_modes[] = {PROGRESSIVE, INTERLACED_MERGED, SEGMENTED_FRAME};
int rgb_shift[] = {0, 8, 16};
switch (property) {
case DISPLAY_PROPERTY_CODECS:
if(sizeof(codecs) <= *len) {
memcpy(val, codecs, sizeof(codecs));
} else {
return FALSE;
}
*len = sizeof(codecs);
break;
case DISPLAY_PROPERTY_RGB_SHIFT:
if(sizeof(rgb_shift) > *len) {
return FALSE;
}
memcpy(val, rgb_shift, sizeof(rgb_shift));
*len = sizeof(rgb_shift);
break;
case DISPLAY_PROPERTY_BUF_PITCH:
*(int *) val = PITCH_DEFAULT;
*len = sizeof(int);
break;
case DISPLAY_PROPERTY_SUPPORTED_IL_MODES:
if(sizeof(supported_il_modes) <= *len) {
memcpy(val, supported_il_modes, sizeof(supported_il_modes));
} else {
return FALSE;
}
*len = sizeof(supported_il_modes);
break;
case DISPLAY_PROPERTY_VIDEO_MODE:
*(int *) val = DISPLAY_PROPERTY_VIDEO_SEPARATE_TILES;
break;
case DISPLAY_PROPERTY_AUDIO_FORMAT:
{
assert(*len == sizeof(struct audio_desc));
struct audio_desc *desc = (struct audio_desc *) val;
desc->sample_rate = 48000;
desc->ch_count = max(desc->ch_count, 4);
desc->codec = AC_PCM;
desc->bps = desc->bps < 3 ? 2 : 3;
}
break;
default:
return FALSE;
}
return TRUE;
}
static int display_bluefish444_reconfigure_audio(void *state, int quant_samples, int channels,
int sample_rate)
{
#ifdef HAVE_BLUE_AUDIO
display_bluefish444_state *s =
(display_bluefish444_state *) state;
int ret;
try {
s->reconfigure_audio(quant_samples, channels, sample_rate);
ret = TRUE;
} catch(runtime_error &e) {
cerr << "[Blue444 disp] " << e.what() << endl;
ret = FALSE;
}
return ret;
#else
return FALSE;
#endif
}
static void display_bluefish444_put_audio_frame(void *state, struct audio_frame *frame)
{
#ifdef HAVE_BLUE_AUDIO
display_bluefish444_state *s =
(display_bluefish444_state *) state;
try {
s->put_audio_frame(frame);
} catch(runtime_error &e) {
cerr << "[Blue444 disp] " << e.what() << endl;
}
#endif
}
static const struct video_display_info display_bluefish444_info = {
display_bluefish444_probe,
display_bluefish444_init,
display_bluefish444_run,
display_bluefish444_done,
display_bluefish444_getf,
display_bluefish444_putf,
display_bluefish444_reconfigure,
display_bluefish444_get_property,
display_bluefish444_put_audio_frame,
display_bluefish444_reconfigure_audio,
DISPLAY_DOESNT_NEED_MAINLOOP,
};
REGISTER_MODULE(bluefish444, &display_bluefish444_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION);