Start of a video mixer node
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@36893 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This class merges buffers together
|
||||||
|
// Merge is called everytime a primary buffer needs to be passed downstream
|
||||||
|
// This should allow different framerates to be handled by buffering slower
|
||||||
|
// buffer producers and discarding buffers from faster producers
|
||||||
|
// TODO ColorConversion
|
||||||
|
|
||||||
|
#include "BufferMixer.h"
|
||||||
|
|
||||||
|
BufferMixer::BufferMixer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferMixer::~BufferMixer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BufferMixer::isBufferAvailable() {
|
||||||
|
return groupedBuffers[0] != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should only be called after checking with isBufferAvailable
|
||||||
|
BBuffer *
|
||||||
|
BufferMixer::GetOutputBuffer() {
|
||||||
|
// Do the merging of all buffers in the groupedBuffers map
|
||||||
|
// into the primary buffer and return that buffer.
|
||||||
|
// The primary buffer is removed;
|
||||||
|
|
||||||
|
BBuffer *outputBuffer = groupedBuffers[0];
|
||||||
|
groupedBuffers[0] = NULL;
|
||||||
|
|
||||||
|
map<int32, BBuffer*>::iterator each;
|
||||||
|
|
||||||
|
for (each=groupedBuffers.begin(); each != groupedBuffers.end(); each++) {
|
||||||
|
if (each->second != outputBuffer) {
|
||||||
|
if (each->second != NULL) {
|
||||||
|
Merge(each->second, outputBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ALPHABLEND(source, destination, alpha) (((destination) * (256 - (alpha)) + (source) * (alpha)) >> 8)
|
||||||
|
|
||||||
|
void
|
||||||
|
BufferMixer::Merge(BBuffer *input, BBuffer *output) {
|
||||||
|
// Currently only deals with RGBA32
|
||||||
|
|
||||||
|
uint8 *source = (uint8 *)input->Data();
|
||||||
|
uint8 *destination = (uint8 *)output->Data();
|
||||||
|
uint32 size = input->Header()->size_used / 4;
|
||||||
|
uint8 alpha = 0;
|
||||||
|
uint8 c1, c2, c3;
|
||||||
|
|
||||||
|
for (uint32 i=0; i<size; i++) {
|
||||||
|
c1 = *source++;
|
||||||
|
c2 = *source++;
|
||||||
|
c3 = *source++;
|
||||||
|
alpha = 128; source++;
|
||||||
|
*destination++ = ALPHABLEND(c1, *destination, alpha);
|
||||||
|
*destination++ = ALPHABLEND(c2, *destination, alpha);
|
||||||
|
*destination++ = ALPHABLEND(c3, *destination, alpha);
|
||||||
|
*destination++ = 0x00;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BufferMixer::AddBuffer(int32 id, BBuffer *buffer, bool isPrimary) {
|
||||||
|
BBuffer *oldBuffer;
|
||||||
|
|
||||||
|
if (isPrimary) {
|
||||||
|
oldBuffer = groupedBuffers[0];
|
||||||
|
groupedBuffers[0] = buffer;
|
||||||
|
} else {
|
||||||
|
oldBuffer = groupedBuffers[id];
|
||||||
|
groupedBuffers[id] = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldBuffer != NULL) {
|
||||||
|
oldBuffer->Recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __BUFFER_MIXER__
|
||||||
|
#define __BUFFER_MIXER__
|
||||||
|
|
||||||
|
#include <media/Buffer.h>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
class BufferMixer {
|
||||||
|
public:
|
||||||
|
BufferMixer();
|
||||||
|
~BufferMixer();
|
||||||
|
|
||||||
|
bool isBufferAvailable();
|
||||||
|
BBuffer *GetOutputBuffer();
|
||||||
|
void AddBuffer(int32 id, BBuffer *buffer, bool isPrimary);
|
||||||
|
void Merge(BBuffer *input, BBuffer *output);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::map<int32, BBuffer *> groupedBuffers;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //__BUFFER_MIXER__
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 David McPaul
|
||||||
|
*
|
||||||
|
* includes code from sysinfo.c which is
|
||||||
|
* Copyright 2004-2008, Axel Dörfler, [email protected].
|
||||||
|
* Copyright (c) 2002, Carlos Hasan, for Haiku.
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <cpu_type.h>
|
||||||
|
|
||||||
|
#include "CpuCapabilities.h"
|
||||||
|
|
||||||
|
CPUCapabilities::~CPUCapabilities()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CPUCapabilities::CPUCapabilities()
|
||||||
|
{
|
||||||
|
#ifdef __INTEL__
|
||||||
|
setIntelCapabilities();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PrintCapabilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CPUCapabilities::setIntelCapabilities()
|
||||||
|
{
|
||||||
|
cpuid_info baseInfo;
|
||||||
|
cpuid_info cpuInfo;
|
||||||
|
int32 maxStandardFunction, maxExtendedFunction = 0;
|
||||||
|
|
||||||
|
if (get_cpuid(&baseInfo, 0L, 0L) != B_OK) {
|
||||||
|
// this CPU doesn't support cpuid
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxStandardFunction = baseInfo.eax_0.max_eax;
|
||||||
|
if (maxStandardFunction >= 500) {
|
||||||
|
maxStandardFunction = 0; /* old Pentium sample chips has cpu signature here */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extended cpuid */
|
||||||
|
|
||||||
|
get_cpuid(&cpuInfo, 0x80000000, 0L);
|
||||||
|
|
||||||
|
// extended cpuid is only supported if max_eax is greater than the service id
|
||||||
|
if (cpuInfo.eax_0.max_eax > 0x80000000) {
|
||||||
|
maxExtendedFunction = cpuInfo.eax_0.max_eax & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxStandardFunction > 0) {
|
||||||
|
|
||||||
|
get_cpuid(&cpuInfo, 1L, 0L);
|
||||||
|
if (cpuInfo.eax_1.features & (1UL << 23)) {
|
||||||
|
capabilities = CAPABILITY_MMX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpuInfo.eax_1.features & (1UL << 25)) {
|
||||||
|
capabilities = CAPABILITY_SSE1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpuInfo.eax_1.features & (1UL << 26)) {
|
||||||
|
capabilities = CAPABILITY_SSE2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxStandardFunction >= 1) {
|
||||||
|
/* Extended features */
|
||||||
|
if (cpuInfo.eax_1.extended_features & (1UL << 0)) {
|
||||||
|
capabilities = CAPABILITY_SSE3;
|
||||||
|
}
|
||||||
|
if (cpuInfo.eax_1.extended_features & (1UL << 9)) {
|
||||||
|
capabilities = CAPABILITY_SSSE3;
|
||||||
|
}
|
||||||
|
if (cpuInfo.eax_1.extended_features & (1UL << 19)) {
|
||||||
|
capabilities = CAPABILITY_SSE41;
|
||||||
|
}
|
||||||
|
if (cpuInfo.eax_1.extended_features & (1UL << 20)) {
|
||||||
|
capabilities = CAPABILITY_SSE42;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasMMX()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_MMX;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasSSE1()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_SSE1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasSSE2()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_SSE2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasSSE3()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_SSE3;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasSSSE3()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_SSSE3;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasSSE41()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_SSE41;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CPUCapabilities::HasSSE42()
|
||||||
|
{
|
||||||
|
return capabilities >= CAPABILITY_SSE42;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CPUCapabilities::PrintCapabilities()
|
||||||
|
{
|
||||||
|
static const char *CapArray[8] = {
|
||||||
|
"", "MMX", "SSE1", "SSE2", "SSE3", "SSSE3", "SSE4.1", "SSE4.2"
|
||||||
|
};
|
||||||
|
|
||||||
|
printf("CPU is capable of running ");
|
||||||
|
if (capabilities) {
|
||||||
|
for (uint32 i=1;i<=capabilities;i++) {
|
||||||
|
printf("%s ",CapArray[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("no extensions");
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __CPU_CAPABILITIES__
|
||||||
|
#define __CPU_CAPABILITIES__
|
||||||
|
|
||||||
|
#define CAPABILITY_MMX 1
|
||||||
|
#define CAPABILITY_SSE1 2
|
||||||
|
#define CAPABILITY_SSE2 3
|
||||||
|
#define CAPABILITY_SSE3 4
|
||||||
|
#define CAPABILITY_SSSE3 5
|
||||||
|
#define CAPABILITY_SSE41 6
|
||||||
|
#define CAPABILITY_SSE42 7
|
||||||
|
|
||||||
|
|
||||||
|
class CPUCapabilities {
|
||||||
|
public:
|
||||||
|
CPUCapabilities();
|
||||||
|
~CPUCapabilities();
|
||||||
|
|
||||||
|
bool HasMMX();
|
||||||
|
bool HasSSE1();
|
||||||
|
bool HasSSE2();
|
||||||
|
bool HasSSE3();
|
||||||
|
bool HasSSSE3();
|
||||||
|
bool HasSSE41();
|
||||||
|
bool HasSSE42();
|
||||||
|
|
||||||
|
void PrintCapabilities();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32 capabilities;
|
||||||
|
|
||||||
|
void setIntelCapabilities();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //__CPU_CAPABILITIES__
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
SubDir HAIKU_TOP src add-ons media media-add-ons video_mixer ;
|
||||||
|
|
||||||
|
UsePrivateHeaders media shared ;
|
||||||
|
|
||||||
|
Addon video_mixer.media_addon :
|
||||||
|
VideoMixerNode.cpp
|
||||||
|
VideoMixerNodeConsumer.cpp
|
||||||
|
VideoMixerNodeProducer.cpp
|
||||||
|
VideoMixerNodeEventLooper.cpp
|
||||||
|
VideoMixerAddOn.cpp
|
||||||
|
CpuCapabilities.cpp
|
||||||
|
BufferMixer.cpp
|
||||||
|
yuvrgb.nasm
|
||||||
|
: be media
|
||||||
|
;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
* VideoMixerAddOn.cpp
|
||||||
|
*
|
||||||
|
* The VideoMixerAddOn class
|
||||||
|
* makes instances of VideoMixerNode
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <Debug.h>
|
||||||
|
#include <MediaDefs.h>
|
||||||
|
#include <MediaAddOn.h>
|
||||||
|
#include <Errors.h>
|
||||||
|
|
||||||
|
#include "VideoMixerNode.h"
|
||||||
|
#include "VideoMixerAddOn.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define DEBUG 1
|
||||||
|
|
||||||
|
// instantiation function
|
||||||
|
extern "C" _EXPORT BMediaAddOn *make_media_addon(image_id image)
|
||||||
|
{
|
||||||
|
return new VideoMixerAddOn(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// ctor/dtor
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
VideoMixerAddOn::~VideoMixerAddOn()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoMixerAddOn::VideoMixerAddOn(image_id image) :
|
||||||
|
BMediaAddOn(image)
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::VideoMixerAddOn\n");
|
||||||
|
refCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// BMediaAddOn impl
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
status_t VideoMixerAddOn::InitCheck(
|
||||||
|
const char **out_failure_text)
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::InitCheck\n");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 VideoMixerAddOn::CountFlavors()
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::CountFlavors\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerAddOn::GetFlavorAt(
|
||||||
|
int32 n,
|
||||||
|
const flavor_info **out_info)
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::GetFlavorAt\n");
|
||||||
|
if (n != 0) {
|
||||||
|
fprintf(stderr,"<- B_BAD_INDEX\n");
|
||||||
|
return B_BAD_INDEX;
|
||||||
|
}
|
||||||
|
flavor_info *infos = new flavor_info[1];
|
||||||
|
VideoMixerNode::GetFlavor(&infos[0], n);
|
||||||
|
(*out_info) = infos;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
BMediaNode * VideoMixerAddOn::InstantiateNodeFor(
|
||||||
|
const flavor_info * info,
|
||||||
|
BMessage * config,
|
||||||
|
status_t * out_error)
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::InstantiateNodeFor\n");
|
||||||
|
VideoMixerNode *node = new VideoMixerNode(info, config, this);
|
||||||
|
if (node == NULL) {
|
||||||
|
*out_error = B_NO_MEMORY;
|
||||||
|
fprintf(stderr,"<- B_NO_MEMORY\n");
|
||||||
|
} else {
|
||||||
|
*out_error = node->InitCheck();
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerAddOn::GetConfigurationFor(
|
||||||
|
BMediaNode * your_node,
|
||||||
|
BMessage * into_message)
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::GetConfigurationFor\n");
|
||||||
|
VideoMixerNode *node = dynamic_cast<VideoMixerNode *>(your_node);
|
||||||
|
if (node == NULL) {
|
||||||
|
fprintf(stderr,"<- B_BAD_TYPE\n");
|
||||||
|
return B_BAD_TYPE;
|
||||||
|
}
|
||||||
|
return node->GetConfigurationFor(into_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoMixerAddOn::WantsAutoStart()
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::WantsAutoStart\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerAddOn::AutoStart(
|
||||||
|
int in_count,
|
||||||
|
BMediaNode **out_node,
|
||||||
|
int32 *out_internal_id,
|
||||||
|
bool *out_has_more)
|
||||||
|
{
|
||||||
|
PRINT("VideoMixerAddOn::AutoStart\n");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// main
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerAddOn cannot be run\n");
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _VIDEO_MIXER_ADD_ON_H
|
||||||
|
#define _VIDEO_MIXER_ADD_ON_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <MediaAddOn.h>
|
||||||
|
|
||||||
|
|
||||||
|
class VideoMixerAddOn : public BMediaAddOn {
|
||||||
|
public:
|
||||||
|
virtual ~VideoMixerAddOn(void);
|
||||||
|
explicit VideoMixerAddOn(image_id image);
|
||||||
|
|
||||||
|
virtual status_t InitCheck(const char **out_failure_text);
|
||||||
|
virtual int32 CountFlavors(void);
|
||||||
|
virtual status_t GetFlavorAt(int32 n,
|
||||||
|
const flavor_info **out_info);
|
||||||
|
virtual BMediaNode* InstantiateNodeFor(const flavor_info *info,
|
||||||
|
BMessage *config, status_t *out_error);
|
||||||
|
virtual status_t GetConfigurationFor(BMediaNode *your_node,
|
||||||
|
BMessage *into_message);
|
||||||
|
virtual bool WantsAutoStart(void);
|
||||||
|
virtual status_t AutoStart(int in_count, BMediaNode **out_node,
|
||||||
|
int32 *out_internal_id,
|
||||||
|
bool *out_has_more);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32 refCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" _EXPORT BMediaAddOn *make_video_mixer_add_on(image_id you);
|
||||||
|
|
||||||
|
#endif /* _VIDEO_MIXER_ADD_ON_H */
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
* VideoMixerNode.cpp
|
||||||
|
*
|
||||||
|
* The VideoMixerNode class takes in multiple video streams and supplies
|
||||||
|
* a single stream as the output.
|
||||||
|
* each stream is converted to the same colourspace and should match
|
||||||
|
* either the primary input OR the requested colourspace from the output
|
||||||
|
* destination.
|
||||||
|
*
|
||||||
|
* The first input is considered the primary input
|
||||||
|
* subsequent input framesize should match the primary input framesize
|
||||||
|
* The output framerate will be the same as the primary input
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "VideoMixerNode.h"
|
||||||
|
|
||||||
|
VideoMixerNode::~VideoMixerNode(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::~VideoMixerNode\n");
|
||||||
|
// Stop the BMediaEventLooper thread
|
||||||
|
Quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoMixerNode::VideoMixerNode(
|
||||||
|
const flavor_info *info = 0,
|
||||||
|
BMessage *config = 0,
|
||||||
|
BMediaAddOn *addOn = 0)
|
||||||
|
: BMediaNode("VideoMixerNode"),
|
||||||
|
BBufferConsumer(B_MEDIA_RAW_VIDEO), // Raw video buffers in
|
||||||
|
BBufferProducer(B_MEDIA_RAW_VIDEO), // Raw video buffers out
|
||||||
|
BMediaEventLooper()
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::VideoMixerNode\n");
|
||||||
|
// keep our creator around for AddOn calls later
|
||||||
|
fAddOn = addOn;
|
||||||
|
// NULL out our latency estimates
|
||||||
|
fDownstreamLatency = 0;
|
||||||
|
fInternalLatency = 0;
|
||||||
|
|
||||||
|
// Start with 1 input and 1 output
|
||||||
|
ClearInput(&fInitialInput);
|
||||||
|
|
||||||
|
strncpy(fOutput.name,"VideoMixer Output", B_MEDIA_NAME_LENGTH-1);
|
||||||
|
fOutput.name[B_MEDIA_NAME_LENGTH-1] = '\0';
|
||||||
|
|
||||||
|
// initialize the output
|
||||||
|
fOutput.node = media_node::null; // until registration
|
||||||
|
fOutput.destination = media_destination::null;
|
||||||
|
fOutput.source.port = ControlPort();
|
||||||
|
fOutput.source.id = 0;
|
||||||
|
|
||||||
|
GetOutputFormat(&fOutput.format);
|
||||||
|
|
||||||
|
fInitCheckStatus = B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::NodeRegistered(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::NodeRegistered\n");
|
||||||
|
|
||||||
|
// for every node created so far set to this Node;
|
||||||
|
for (uint32 i=0;i<fConnectedInputs.size();i++) {
|
||||||
|
fConnectedInputs[i]->node = Node();
|
||||||
|
fConnectedInputs[i]->destination.id = i;
|
||||||
|
fConnectedInputs[i]->destination.port = ControlPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
fInitialInput.node = Node();
|
||||||
|
fInitialInput.destination.id = fConnectedInputs.size();
|
||||||
|
fInitialInput.destination.port = ControlPort();
|
||||||
|
|
||||||
|
GetOutputFormat(&fOutput.format);
|
||||||
|
fOutput.node = Node();
|
||||||
|
|
||||||
|
// start the BMediaEventLooper thread
|
||||||
|
SetPriority(B_REAL_TIME_PRIORITY);
|
||||||
|
Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *
|
||||||
|
VideoMixerNode::CreateInput(uint32 inputID) {
|
||||||
|
media_input *input = new media_input();
|
||||||
|
|
||||||
|
ClearInput(input);
|
||||||
|
|
||||||
|
// don't overwrite available space, and be sure to terminate
|
||||||
|
sprintf(input->name, "VideoMixer Input %ld", inputID);
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VideoMixerNode::ClearInput(media_input *input) {
|
||||||
|
|
||||||
|
// initialize the input
|
||||||
|
sprintf(input->name, "VideoMixer Input");
|
||||||
|
input->node = Node();
|
||||||
|
input->source = media_source::null;
|
||||||
|
input->destination = media_destination::null;
|
||||||
|
|
||||||
|
GetInputFormat(&input->format);
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *
|
||||||
|
VideoMixerNode::GetInput(const media_source &source) {
|
||||||
|
|
||||||
|
vector<media_input *>::iterator each;
|
||||||
|
|
||||||
|
for (each=fConnectedInputs.begin(); each<fConnectedInputs.end(); each++) {
|
||||||
|
if ((*each)->source == source) {
|
||||||
|
return *each;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *
|
||||||
|
VideoMixerNode::GetInput(const media_destination &destination) {
|
||||||
|
|
||||||
|
vector<media_input *>::iterator each;
|
||||||
|
|
||||||
|
for (each=fConnectedInputs.begin(); each<fConnectedInputs.end(); each++) {
|
||||||
|
if ((*each)->destination == destination) {
|
||||||
|
return *each;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *
|
||||||
|
VideoMixerNode::GetInput(const int32 id) {
|
||||||
|
|
||||||
|
vector<media_input *>::iterator each;
|
||||||
|
|
||||||
|
for (each=fConnectedInputs.begin(); each<fConnectedInputs.end(); each++) {
|
||||||
|
if ((*each)->destination.id == id) {
|
||||||
|
return *each;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::InitCheck(void) const
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::InitCheck\n");
|
||||||
|
return fInitCheckStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::GetConfigurationFor(
|
||||||
|
BMessage *into_message)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::GetConfigurationFor\n");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// implementation of BMediaNode
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
BMediaAddOn *VideoMixerNode::AddOn(
|
||||||
|
int32 *internal_id) const
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::AddOn\n");
|
||||||
|
// BeBook says this only gets called if we were in an add-on.
|
||||||
|
if (fAddOn != NULL) {
|
||||||
|
// If we get a null pointer then we just won't write.
|
||||||
|
if (internal_id != NULL) {
|
||||||
|
internal_id = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fAddOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Start(bigtime_t performance_time)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::Start(pt=%lld)\n", performance_time);
|
||||||
|
BMediaEventLooper::Start(performance_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Stop(
|
||||||
|
bigtime_t performance_time,
|
||||||
|
bool immediate)
|
||||||
|
{
|
||||||
|
if (immediate) {
|
||||||
|
fprintf(stderr,"VideoMixerNode::Stop(pt=%lld,<immediate>)\n", performance_time);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr,"VideoMixerNode::Stop(pt=%lld,<scheduled>)\n", performance_time);
|
||||||
|
}
|
||||||
|
BMediaEventLooper::Stop(performance_time, immediate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Seek(
|
||||||
|
bigtime_t media_time,
|
||||||
|
bigtime_t performance_time)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::Seek(mt=%lld,pt=%lld)\n", media_time,performance_time);
|
||||||
|
BMediaEventLooper::Seek(media_time, performance_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::SetRunMode(run_mode mode)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::SetRunMode(%i)\n", mode);
|
||||||
|
BMediaEventLooper::SetRunMode(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::TimeWarp(
|
||||||
|
bigtime_t at_real_time,
|
||||||
|
bigtime_t to_performance_time)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::TimeWarp(rt=%lld,pt=%lld)\n", at_real_time, to_performance_time);
|
||||||
|
BMediaEventLooper::TimeWarp(at_real_time, to_performance_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Preroll(void)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::Preroll\n");
|
||||||
|
// XXX:Performance opportunity
|
||||||
|
BMediaNode::Preroll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::SetTimeSource(BTimeSource *time_source)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::SetTimeSource\n");
|
||||||
|
BMediaNode::SetTimeSource(time_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleMessage(
|
||||||
|
int32 message,
|
||||||
|
const void *data,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::HandleMessage\n");
|
||||||
|
status_t status = B_OK;
|
||||||
|
switch (message) {
|
||||||
|
// no special messages for now
|
||||||
|
default:
|
||||||
|
status = BBufferConsumer::HandleMessage(message, data, size);
|
||||||
|
if (status == B_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
status = BBufferProducer::HandleMessage(message, data, size);
|
||||||
|
if (status == B_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
status = BMediaNode::HandleMessage(message, data, size);
|
||||||
|
if (status == B_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
BMediaNode::HandleBadMessage(message, data, size);
|
||||||
|
status = B_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::RequestCompleted(const media_request_info &info)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::RequestCompleted\n");
|
||||||
|
return BMediaNode::RequestCompleted(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::DeleteHook(BMediaNode *node)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::DeleteHook\n");
|
||||||
|
return BMediaEventLooper::DeleteHook(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::GetNodeAttributes(
|
||||||
|
media_node_attribute *outAttributes,
|
||||||
|
size_t inMaxCount)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::GetNodeAttributes\n");
|
||||||
|
return BMediaNode::GetNodeAttributes(outAttributes, inMaxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::AddTimer(
|
||||||
|
bigtime_t at_performance_time,
|
||||||
|
int32 cookie)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::AddTimer\n");
|
||||||
|
return BMediaEventLooper::AddTimer(at_performance_time, cookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// VideoMixerNode specific functions
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
// public:
|
||||||
|
|
||||||
|
void VideoMixerNode::GetFlavor(flavor_info *outInfo, int32 id)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::GetFlavor\n");
|
||||||
|
|
||||||
|
if (outInfo != NULL) {
|
||||||
|
outInfo->internal_id = id;
|
||||||
|
outInfo->name = "Haiku VideoMixer";
|
||||||
|
outInfo->info = "A VideoMixerNode node mixes multiple video streams into a single stream.";
|
||||||
|
outInfo->kinds = B_BUFFER_CONSUMER | B_BUFFER_PRODUCER;
|
||||||
|
outInfo->flavor_flags = B_FLAVOR_IS_LOCAL;
|
||||||
|
outInfo->possible_count = INT_MAX; // no limit
|
||||||
|
outInfo->in_format_count = 1;
|
||||||
|
media_format *inFormats = new media_format[outInfo->in_format_count];
|
||||||
|
GetInputFormat(&inFormats[0]);
|
||||||
|
outInfo->in_formats = inFormats;
|
||||||
|
outInfo->out_format_count = 1; // single output
|
||||||
|
media_format *outFormats = new media_format[outInfo->out_format_count];
|
||||||
|
GetOutputFormat(&outFormats[0]);
|
||||||
|
outInfo->out_formats = outFormats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::GetInputFormat(media_format *outFormat)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::GetInputFormat\n");
|
||||||
|
|
||||||
|
if (outFormat != NULL) {
|
||||||
|
outFormat->type = B_MEDIA_RAW_VIDEO;
|
||||||
|
outFormat->require_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
|
||||||
|
outFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
|
||||||
|
outFormat->u.raw_video = media_raw_video_format::wildcard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::GetOutputFormat(media_format *outFormat)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::GetOutputFormat\n");
|
||||||
|
if (outFormat != NULL) {
|
||||||
|
outFormat->type = B_MEDIA_RAW_VIDEO;
|
||||||
|
outFormat->require_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
|
||||||
|
outFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
|
||||||
|
outFormat->u.raw_video = media_raw_video_format::wildcard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// protected:
|
||||||
|
|
||||||
|
status_t VideoMixerNode::AddRequirements(media_format *format)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode::AddRequirements\n");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,330 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _VIDEO_MIXER_NODE_H
|
||||||
|
#define _VIDEO_MIXER_NODE_H
|
||||||
|
|
||||||
|
#include <Buffer.h>
|
||||||
|
#include <BufferConsumer.h>
|
||||||
|
#include <BufferGroup.h>
|
||||||
|
#include <BufferProducer.h>
|
||||||
|
#include <MediaAddOn.h>
|
||||||
|
#include <MediaDefs.h>
|
||||||
|
#include <MediaEventLooper.h>
|
||||||
|
#include <MediaNode.h>
|
||||||
|
#include <TimeSource.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "BufferMixer.h"
|
||||||
|
|
||||||
|
class VideoMixerNode :
|
||||||
|
public BBufferConsumer,
|
||||||
|
public BBufferProducer,
|
||||||
|
public BMediaEventLooper
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
virtual ~VideoMixerNode(void);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit VideoMixerNode(
|
||||||
|
const flavor_info * info = 0,
|
||||||
|
BMessage *config = 0,
|
||||||
|
BMediaAddOn *addOn = 0);
|
||||||
|
|
||||||
|
virtual status_t InitCheck(void) const;
|
||||||
|
|
||||||
|
// see BMediaAddOn::GetConfigurationFor
|
||||||
|
virtual status_t GetConfigurationFor(
|
||||||
|
BMessage *into_message);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// /* this port is what a media node listens to for commands */
|
||||||
|
// virtual port_id ControlPort(void) const;
|
||||||
|
|
||||||
|
virtual BMediaAddOn* AddOn(
|
||||||
|
int32 *internal_id) const; /* Who instantiated you -- or NULL for app class */
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* These don't return errors; instead, they use the global error condition reporter. */
|
||||||
|
/* A node is required to have a queue of at least one pending command (plus TimeWarp) */
|
||||||
|
/* and is recommended to allow for at least one pending command of each type. */
|
||||||
|
/* Allowing an arbitrary number of outstanding commands might be nice, but apps */
|
||||||
|
/* cannot depend on that happening. */
|
||||||
|
virtual void Start(
|
||||||
|
bigtime_t performance_time);
|
||||||
|
virtual void Stop(
|
||||||
|
bigtime_t performance_time,
|
||||||
|
bool immediate);
|
||||||
|
virtual void Seek(
|
||||||
|
bigtime_t media_time,
|
||||||
|
bigtime_t performance_time);
|
||||||
|
virtual void SetRunMode(
|
||||||
|
run_mode mode);
|
||||||
|
virtual void TimeWarp(
|
||||||
|
bigtime_t at_real_time,
|
||||||
|
bigtime_t to_performance_time);
|
||||||
|
virtual void Preroll(void);
|
||||||
|
virtual void SetTimeSource(BTimeSource *time_source);
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual status_t HandleMessage(
|
||||||
|
int32 message,
|
||||||
|
const void *data,
|
||||||
|
size_t size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* Called when requests have completed, or failed. */
|
||||||
|
virtual status_t RequestCompleted( /* reserved 0 */
|
||||||
|
const media_request_info &info);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual status_t DeleteHook(BMediaNode *node); /* reserved 1 */
|
||||||
|
|
||||||
|
virtual void NodeRegistered(void); /* reserved 2 */
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/* fill out your attributes in the provided array, returning however many you have. */
|
||||||
|
virtual status_t GetNodeAttributes( /* reserved 3 */
|
||||||
|
media_node_attribute *outAttributes,
|
||||||
|
size_t inMaxCount);
|
||||||
|
|
||||||
|
virtual status_t AddTimer(
|
||||||
|
bigtime_t at_performance_time,
|
||||||
|
int32 cookie);
|
||||||
|
|
||||||
|
/* Someone, probably the producer, is asking you about this format. Give */
|
||||||
|
/* your honest opinion, possibly modifying *format. Do not ask upstream */
|
||||||
|
/* producer about the format, since he's synchronously waiting for your */
|
||||||
|
/* reply. */
|
||||||
|
virtual status_t AcceptFormat(
|
||||||
|
const media_destination &dest,
|
||||||
|
media_format *format);
|
||||||
|
virtual status_t GetNextInput(
|
||||||
|
int32 * cookie,
|
||||||
|
media_input *out_input);
|
||||||
|
virtual void DisposeInputCookie(int32 cookie);
|
||||||
|
virtual void BufferReceived(BBuffer *buffer);
|
||||||
|
virtual void ProducerDataStatus(
|
||||||
|
const media_destination &for_whom,
|
||||||
|
int32 status,
|
||||||
|
bigtime_t at_performance_time);
|
||||||
|
virtual status_t GetLatencyFor(
|
||||||
|
const media_destination &for_whom,
|
||||||
|
bigtime_t *out_latency,
|
||||||
|
media_node_id *out_timesource);
|
||||||
|
virtual status_t Connected(
|
||||||
|
const media_source &producer, /* here's a good place to request buffer group usage */
|
||||||
|
const media_destination &where,
|
||||||
|
const media_format &with_format,
|
||||||
|
media_input *out_input);
|
||||||
|
virtual void Disconnected(
|
||||||
|
const media_source &producer,
|
||||||
|
const media_destination &where);
|
||||||
|
/* The notification comes from the upstream producer, so he's already cool with */
|
||||||
|
/* the format; you should not ask him about it in here. */
|
||||||
|
virtual status_t FormatChanged(
|
||||||
|
const media_source &producer,
|
||||||
|
const media_destination &consumer,
|
||||||
|
int32 change_tag,
|
||||||
|
const media_format &format);
|
||||||
|
|
||||||
|
/* Given a performance time of some previous buffer, retrieve the remembered tag */
|
||||||
|
/* of the closest (previous or exact) performance time. Set *out_flags to 0; the */
|
||||||
|
/* idea being that flags can be added later, and the understood flags returned in */
|
||||||
|
/* *out_flags. */
|
||||||
|
virtual status_t SeekTagRequested(
|
||||||
|
const media_destination &destination,
|
||||||
|
bigtime_t in_target_time,
|
||||||
|
uint32 in_flags,
|
||||||
|
media_seek_tag *out_seek_tag,
|
||||||
|
bigtime_t *out_tagged_time,
|
||||||
|
uint32 *out_flags);
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* functionality of BBufferProducer */
|
||||||
|
virtual status_t FormatSuggestionRequested(
|
||||||
|
media_type type,
|
||||||
|
int32 quality,
|
||||||
|
media_format *format);
|
||||||
|
virtual status_t FormatProposal(
|
||||||
|
const media_source &output,
|
||||||
|
media_format *format);
|
||||||
|
/* If the format isn't good, put a good format into *io_format and return error */
|
||||||
|
/* If format has wildcard, specialize to what you can do (and change). */
|
||||||
|
/* If you can change the format, return OK. */
|
||||||
|
/* The request comes from your destination sychronously, so you cannot ask it */
|
||||||
|
/* whether it likes it -- you should assume it will since it asked. */
|
||||||
|
virtual status_t FormatChangeRequested(
|
||||||
|
const media_source &source,
|
||||||
|
const media_destination &destination,
|
||||||
|
media_format *io_format,
|
||||||
|
int32 *_deprecated_);
|
||||||
|
virtual status_t GetNextOutput( /* cookie starts as 0 */
|
||||||
|
int32 *cookie,
|
||||||
|
media_output *out_output);
|
||||||
|
virtual status_t DisposeOutputCookie(
|
||||||
|
int32 cookie);
|
||||||
|
/* In this function, you should either pass on the group to your upstream guy, */
|
||||||
|
/* or delete your current group and hang on to this group. Deleting the previous */
|
||||||
|
/* group (unless you passed it on with the reclaim flag set to false) is very */
|
||||||
|
/* important, else you will 1) leak memory and 2) block someone who may want */
|
||||||
|
/* to reclaim the buffers living in that group. */
|
||||||
|
virtual status_t SetBufferGroup(
|
||||||
|
const media_source &for_source,
|
||||||
|
BBufferGroup * group);
|
||||||
|
/* Format of clipping is (as int16-s): <from line> <npairs> <startclip> <endclip>. */
|
||||||
|
/* Repeat for each line where the clipping is different from the previous line. */
|
||||||
|
/* If <npairs> is negative, use the data from line -<npairs> (there are 0 pairs after */
|
||||||
|
/* a negative <npairs>. Yes, we only support 32k*32k frame buffers for clipping. */
|
||||||
|
/* Any non-0 field of 'display' means that that field changed, and if you don't support */
|
||||||
|
/* that change, you should return an error and ignore the request. Note that the buffer */
|
||||||
|
/* offset values do not have wildcards; 0 (or -1, or whatever) are real values and must */
|
||||||
|
/* be adhered to. */
|
||||||
|
virtual status_t VideoClippingChanged(
|
||||||
|
const media_source &for_source,
|
||||||
|
int16 num_shorts,
|
||||||
|
int16 *clip_data,
|
||||||
|
const media_video_display_info &display,
|
||||||
|
int32 *_deprecated_);
|
||||||
|
/* Iterates over all outputs and maxes the latency found */
|
||||||
|
virtual status_t GetLatency(bigtime_t *out_latency);
|
||||||
|
virtual status_t PrepareToConnect(
|
||||||
|
const media_source &what,
|
||||||
|
const media_destination &where,
|
||||||
|
media_format *format,
|
||||||
|
media_source *out_source,
|
||||||
|
char *out_name);
|
||||||
|
virtual void Connect(
|
||||||
|
status_t error,
|
||||||
|
const media_source &source,
|
||||||
|
const media_destination &destination,
|
||||||
|
const media_format &format,
|
||||||
|
char *io_name);
|
||||||
|
virtual void Disconnect(
|
||||||
|
const media_source &what,
|
||||||
|
const media_destination &where);
|
||||||
|
virtual void LateNoticeReceived(
|
||||||
|
const media_source &what,
|
||||||
|
bigtime_t how_much,
|
||||||
|
bigtime_t performance_time);
|
||||||
|
virtual void EnableOutput(
|
||||||
|
const media_source &what,
|
||||||
|
bool enabled,
|
||||||
|
int32 *_deprecated_);
|
||||||
|
virtual status_t SetPlayRate(
|
||||||
|
int32 numer,
|
||||||
|
int32 denom);
|
||||||
|
|
||||||
|
virtual void AdditionalBufferRequested( // used to be Reserved 0
|
||||||
|
const media_source & source,
|
||||||
|
media_buffer_id prev_buffer,
|
||||||
|
bigtime_t prev_time,
|
||||||
|
const media_seek_tag *prev_tag); // may be NULL
|
||||||
|
|
||||||
|
virtual void LatencyChanged( // used to be Reserved 1
|
||||||
|
const media_source & source,
|
||||||
|
const media_destination & destination,
|
||||||
|
bigtime_t new_latency,
|
||||||
|
uint32 flags);
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* you must override to handle your events! */
|
||||||
|
/* you should not call HandleEvent directly */
|
||||||
|
virtual void HandleEvent( const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
|
||||||
|
/* override to clean up custom events you have added to your queue */
|
||||||
|
virtual void CleanUpEvent(const media_timed_event *event);
|
||||||
|
|
||||||
|
/* called from Offline mode to determine the current time of the node */
|
||||||
|
/* update your internal information whenever it changes */
|
||||||
|
virtual bigtime_t OfflineTime();
|
||||||
|
|
||||||
|
/* override only if you know what you are doing! */
|
||||||
|
/* otherwise much badness could occur */
|
||||||
|
/* the actual control loop function: */
|
||||||
|
/* waits for messages, Pops events off the queue and calls DispatchEvent */
|
||||||
|
virtual void ControlLoop();
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual status_t HandleStart(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
virtual status_t HandleSeek(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
virtual status_t HandleWarp(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
virtual status_t HandleStop(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
virtual status_t HandleBuffer(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
virtual status_t HandleDataStatus(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
virtual status_t HandleParameter(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
//void CreateBufferGroup(MediaOutputInfo *output_info);
|
||||||
|
void ComputeInternalLatency();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static void GetFlavor(flavor_info *outInfo, int32 id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
media_input *CreateInput(uint32 inputID);
|
||||||
|
void ClearInput(media_input *input);
|
||||||
|
media_input *GetInput(const media_source &source);
|
||||||
|
media_input *GetInput(const media_destination &destination);
|
||||||
|
media_input *GetInput(const int32 id);
|
||||||
|
|
||||||
|
static void GetInputFormat(media_format *outFormat);
|
||||||
|
static void GetOutputFormat(media_format *outFormat);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual status_t AddRequirements(media_format *format);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
status_t fInitCheckStatus;
|
||||||
|
|
||||||
|
BMediaAddOn *fAddOn;
|
||||||
|
|
||||||
|
media_input fInitialInput;
|
||||||
|
std::vector<media_input *> fConnectedInputs;
|
||||||
|
media_output fOutput;
|
||||||
|
|
||||||
|
bigtime_t fDownstreamLatency;
|
||||||
|
bigtime_t fInternalLatency;
|
||||||
|
|
||||||
|
BufferMixer bufferMixer;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _VIDEO_MIXER_NODE_H */
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
* VideoMixerNode.cpp
|
||||||
|
*
|
||||||
|
* The VideoMixerNode class
|
||||||
|
* takes in multiple video streams and supplies
|
||||||
|
* a single stream as the output.
|
||||||
|
* each stream is converted to the same colourspace
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "VideoMixerNode.h"
|
||||||
|
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// implemention of BBufferConsumer
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
// Check to make sure the format is okay, then remove
|
||||||
|
// any wildcards corresponding to our requirements.
|
||||||
|
status_t VideoMixerNode::AcceptFormat(
|
||||||
|
const media_destination &dest,
|
||||||
|
media_format *format)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::AcceptFormat\n");
|
||||||
|
|
||||||
|
if (fInitialInput.destination != dest) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION");
|
||||||
|
return B_MEDIA_BAD_DESTINATION; // none of our inputs matched the dest
|
||||||
|
}
|
||||||
|
|
||||||
|
media_format myFormat;
|
||||||
|
|
||||||
|
GetInputFormat(&myFormat);
|
||||||
|
|
||||||
|
AddRequirements(format);
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::GetNextInput(
|
||||||
|
int32 *cookie,
|
||||||
|
media_input *out_input)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::GetNextInput (%ld)\n",*cookie);
|
||||||
|
|
||||||
|
// Cookie 0 is the connecting input, all others are connected inputs
|
||||||
|
if (*cookie == fConnectedInputs.size()) {
|
||||||
|
*out_input = fInitialInput;
|
||||||
|
} else {
|
||||||
|
out_input = GetInput(*cookie);
|
||||||
|
|
||||||
|
if (out_input == NULL) {
|
||||||
|
fprintf(stderr,"<- B_ERROR (no more inputs)\n");
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// so next time they won't get the same input again
|
||||||
|
(*cookie)++;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::DisposeInputCookie(
|
||||||
|
int32 cookie)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::DisposeInputCookie\n");
|
||||||
|
// nothing to do since our cookies are just integers
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::BufferReceived(BBuffer *buffer)
|
||||||
|
{
|
||||||
|
switch (buffer->Header()->type) {
|
||||||
|
// case B_MEDIA_PARAMETERS:
|
||||||
|
// {
|
||||||
|
// status_t status = ApplyParameterData(buffer->Data(),buffer->SizeUsed());
|
||||||
|
// if (status != B_OK) {
|
||||||
|
// fprintf(stderr,"ApplyParameterData in MediaDemultiplexerNode::BufferReceived failed\n");
|
||||||
|
// }
|
||||||
|
// buffer->Recycle();
|
||||||
|
// }
|
||||||
|
// break;
|
||||||
|
case B_MEDIA_RAW_VIDEO:
|
||||||
|
if (buffer->Flags() & BBuffer::B_SMALL_BUFFER) {
|
||||||
|
fprintf(stderr,"NOT IMPLEMENTED: B_SMALL_BUFFER in VideoMixerNode::BufferReceived\n");
|
||||||
|
// XXX: implement this part
|
||||||
|
buffer->Recycle();
|
||||||
|
} else {
|
||||||
|
media_timed_event event(buffer->Header()->start_time, BTimedEventQueue::B_HANDLE_BUFFER,
|
||||||
|
buffer, BTimedEventQueue::B_RECYCLE_BUFFER);
|
||||||
|
status_t status = EventQueue()->AddEvent(event);
|
||||||
|
if (status != B_OK) {
|
||||||
|
fprintf(stderr,"EventQueue()->AddEvent(event) in VideoMixerNode::BufferReceived failed\n");
|
||||||
|
buffer->Recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr,"unexpected buffer type in VideoMixerNode::BufferReceived\n");
|
||||||
|
buffer->Recycle();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::ProducerDataStatus(
|
||||||
|
const media_destination &for_whom,
|
||||||
|
int32 status,
|
||||||
|
bigtime_t at_performance_time)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::ProducerDataStatus\n");
|
||||||
|
media_input *input = GetInput(for_whom);
|
||||||
|
|
||||||
|
if (input == NULL) {
|
||||||
|
fprintf(stderr,"invalid destination received in VideoMixerNode::ProducerDataStatus\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
media_timed_event event(at_performance_time, BTimedEventQueue::B_DATA_STATUS,
|
||||||
|
&input, BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL);
|
||||||
|
EventQueue()->AddEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::GetLatencyFor(
|
||||||
|
const media_destination &for_whom,
|
||||||
|
bigtime_t *out_latency,
|
||||||
|
media_node_id *out_timesource)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::GetLatencyFor\n");
|
||||||
|
if ((out_latency == 0) || (out_timesource == 0)) {
|
||||||
|
fprintf(stderr,"<- B_BAD_VALUE\n");
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *input = GetInput(for_whom);
|
||||||
|
|
||||||
|
if (input == NULL) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
|
||||||
|
return B_MEDIA_BAD_DESTINATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_latency = EventLatency();
|
||||||
|
*out_timesource = TimeSource()->ID();
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::Connected(
|
||||||
|
const media_source &producer, /* here's a good place to request buffer group usage */
|
||||||
|
const media_destination &where,
|
||||||
|
const media_format &with_format,
|
||||||
|
media_input *out_input)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::Connected\n");
|
||||||
|
|
||||||
|
if (fInitialInput.destination != where) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
|
||||||
|
return B_MEDIA_BAD_DESTINATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *input = CreateInput(fConnectedInputs.size());
|
||||||
|
fConnectedInputs.push_back(input);
|
||||||
|
|
||||||
|
// Specialise the output?
|
||||||
|
|
||||||
|
// compute the latency or just guess
|
||||||
|
fInternalLatency = 500; // just a guess
|
||||||
|
fprintf(stderr," internal latency guessed = %lld\n", fInternalLatency);
|
||||||
|
|
||||||
|
SetEventLatency(fInternalLatency);
|
||||||
|
|
||||||
|
// record the agreed upon values
|
||||||
|
input->destination = where;
|
||||||
|
input->source = producer;
|
||||||
|
input->format = with_format;
|
||||||
|
|
||||||
|
*out_input = *input;
|
||||||
|
|
||||||
|
// Reset the Initial Input
|
||||||
|
ClearInput(&fInitialInput);
|
||||||
|
fInitialInput.destination.id = fConnectedInputs.size();
|
||||||
|
fInitialInput.destination.port = ControlPort();
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Disconnected(
|
||||||
|
const media_source &producer,
|
||||||
|
const media_destination &where)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::Disconnected\n");
|
||||||
|
|
||||||
|
media_input *input = GetInput(where);
|
||||||
|
|
||||||
|
if (input == NULL) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input->source != producer) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// disconnected but not deleted (important)
|
||||||
|
input->source = media_source::null;
|
||||||
|
GetInputFormat(&input->format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The notification comes from the upstream producer, so he's already cool with */
|
||||||
|
/* the format; you should not ask him about it in here. */
|
||||||
|
status_t VideoMixerNode::FormatChanged(
|
||||||
|
const media_source & producer,
|
||||||
|
const media_destination & consumer,
|
||||||
|
int32 change_tag,
|
||||||
|
const media_format & format)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::FormatChanged\n");
|
||||||
|
|
||||||
|
media_input *input = GetInput(producer);
|
||||||
|
|
||||||
|
if (input == NULL) {
|
||||||
|
return B_MEDIA_BAD_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input->destination != consumer) {
|
||||||
|
return B_MEDIA_BAD_DESTINATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
input->format = format;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Given a performance time of some previous buffer, retrieve the remembered tag */
|
||||||
|
/* of the closest (previous or exact) performance time. Set *out_flags to 0; the */
|
||||||
|
/* idea being that flags can be added later, and the understood flags returned in */
|
||||||
|
/* *out_flags. */
|
||||||
|
status_t VideoMixerNode::SeekTagRequested(
|
||||||
|
const media_destination & destination,
|
||||||
|
bigtime_t in_target_time,
|
||||||
|
uint32 in_flags,
|
||||||
|
media_seek_tag * out_seek_tag,
|
||||||
|
bigtime_t * out_tagged_time,
|
||||||
|
uint32 * out_flags)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferConsumer)::SeekTagRequested\n");
|
||||||
|
// XXX: implement this
|
||||||
|
return BBufferConsumer::SeekTagRequested(destination,in_target_time, in_flags,
|
||||||
|
out_seek_tag, out_tagged_time, out_flags);
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
* VideoMixerNode.cpp
|
||||||
|
*
|
||||||
|
* The VideoMixerNode class
|
||||||
|
* takes in multiple video streams and supplies
|
||||||
|
* a single stream as the output.
|
||||||
|
* each stream is converted to the same colourspace
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "VideoMixerNode.h"
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// implementation for BMediaEventLooper
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
void VideoMixerNode::HandleEvent(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
switch (event->type) {
|
||||||
|
case BTimedEventQueue::B_START:
|
||||||
|
HandleStart(event,lateness,realTimeEvent);
|
||||||
|
break;
|
||||||
|
case BTimedEventQueue::B_SEEK:
|
||||||
|
HandleSeek(event,lateness,realTimeEvent);
|
||||||
|
break;
|
||||||
|
case BTimedEventQueue::B_WARP:
|
||||||
|
HandleWarp(event,lateness,realTimeEvent);
|
||||||
|
break;
|
||||||
|
case BTimedEventQueue::B_STOP:
|
||||||
|
HandleStop(event,lateness,realTimeEvent);
|
||||||
|
break;
|
||||||
|
case BTimedEventQueue::B_HANDLE_BUFFER:
|
||||||
|
if (RunState() == BMediaEventLooper::B_STARTED) {
|
||||||
|
HandleBuffer(event,lateness,realTimeEvent);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BTimedEventQueue::B_DATA_STATUS:
|
||||||
|
HandleDataStatus(event, lateness, realTimeEvent);
|
||||||
|
break;
|
||||||
|
case BTimedEventQueue::B_PARAMETER:
|
||||||
|
HandleParameter(event,lateness,realTimeEvent);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr," unknown event type: %ld\n",event->type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* override to clean up custom events you have added to your queue */
|
||||||
|
void VideoMixerNode::CleanUpEvent(
|
||||||
|
const media_timed_event *event)
|
||||||
|
{
|
||||||
|
BMediaEventLooper::CleanUpEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* called from Offline mode to determine the current time of the node */
|
||||||
|
/* update your internal information whenever it changes */
|
||||||
|
bigtime_t VideoMixerNode::OfflineTime()
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::OfflineTime\n");
|
||||||
|
return BMediaEventLooper::OfflineTime();
|
||||||
|
// XXX: do something else?
|
||||||
|
}
|
||||||
|
|
||||||
|
/* override only if you know what you are doing! */
|
||||||
|
/* otherwise much badness could occur */
|
||||||
|
/* the actual control loop function: */
|
||||||
|
/* waits for messages, Pops events off the queue and calls DispatchEvent */
|
||||||
|
void VideoMixerNode::ControlLoop() {
|
||||||
|
BMediaEventLooper::ControlLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// protected:
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleStart(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::HandleStart()\n");
|
||||||
|
if (RunState() != B_STARTED) {
|
||||||
|
media_timed_event firstBufferEvent(event->event_time, BTimedEventQueue::B_HANDLE_BUFFER);
|
||||||
|
HandleEvent(&firstBufferEvent, 0, false);
|
||||||
|
EventQueue()->AddEvent(firstBufferEvent);
|
||||||
|
}
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleSeek(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::HandleSeek(t=%lld,d=%ld,bd=%lld)\n",event->event_time, event->data, event->bigdata);
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleWarp(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::HandleWarp\n");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleStop(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::HandleStop\n");
|
||||||
|
// flush the queue so downstreamers don't get any more
|
||||||
|
EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleBuffer(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
if (event->type != BTimedEventQueue::B_HANDLE_BUFFER) {
|
||||||
|
fprintf(stderr,"HandleBuffer called on non buffer event type\n");
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
BBuffer *buffer = const_cast<BBuffer*>((BBuffer*)event->pointer);
|
||||||
|
if (buffer == NULL) {
|
||||||
|
fprintf(stderr,"NO BUFFER PASSED\n");
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
media_input *input = GetInput(buffer->Header()->destination);
|
||||||
|
|
||||||
|
if (input == NULL) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
|
||||||
|
return B_MEDIA_BAD_DESTINATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fOutput.format.u.raw_video == media_raw_video_format::wildcard) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_NOT_CONNECTED\n");
|
||||||
|
return B_MEDIA_NOT_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t status = B_OK;
|
||||||
|
|
||||||
|
if (input == *fConnectedInputs.begin()) {
|
||||||
|
if (bufferMixer.isBufferAvailable()) {
|
||||||
|
status = SendBuffer(bufferMixer.GetOutputBuffer(), fOutput.source, fOutput.destination);
|
||||||
|
}
|
||||||
|
bufferMixer.AddBuffer(input->destination.id, buffer, true);
|
||||||
|
} else {
|
||||||
|
bufferMixer.AddBuffer(input->destination.id, buffer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleDataStatus(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::HandleDataStatus");
|
||||||
|
SendDataStatus(event->data, fOutput.destination, event->event_time);
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::HandleParameter(
|
||||||
|
const media_timed_event *event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent = false)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BMediaEventLooper)::HandleParameter");
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2009-2010 David McPaul
|
||||||
|
*
|
||||||
|
* All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
* VideoMixerNode.cpp
|
||||||
|
*
|
||||||
|
* The VideoMixerNode class
|
||||||
|
* takes in multiple video streams and supplies
|
||||||
|
* a single stream as the output.
|
||||||
|
* each stream is converted to the same colourspace
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "VideoMixerNode.h"
|
||||||
|
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
// implemention of BBufferProducer
|
||||||
|
// -------------------------------------------------------- //
|
||||||
|
|
||||||
|
// They are asking us to make the first offering.
|
||||||
|
// So, we get a fresh format and then add requirements
|
||||||
|
status_t VideoMixerNode::FormatSuggestionRequested(
|
||||||
|
media_type type,
|
||||||
|
int32 quality,
|
||||||
|
media_format * format)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::FormatSuggestionRequested\n");
|
||||||
|
|
||||||
|
if (format->type == B_MEDIA_NO_TYPE) {
|
||||||
|
format->type = B_MEDIA_RAW_VIDEO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format->type != B_MEDIA_RAW_VIDEO) {
|
||||||
|
return B_MEDIA_BAD_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetOutputFormat(format);
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// They made an offer to us. We should make sure that the offer is
|
||||||
|
// acceptable, and then we can add any requirements we have on top of
|
||||||
|
// that. We leave wildcards for anything that we don't care about.
|
||||||
|
status_t VideoMixerNode::FormatProposal(
|
||||||
|
const media_source &output_source,
|
||||||
|
media_format *format)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::FormatProposal\n");
|
||||||
|
|
||||||
|
fOutput.source = output_source;
|
||||||
|
|
||||||
|
// If we have an input then set our output as the same except for color_space
|
||||||
|
if (fConnectedInputs.size() > 0) {
|
||||||
|
if (fOutput.format.u.raw_video == media_raw_video_format::wildcard) {
|
||||||
|
// First proposal
|
||||||
|
fOutput.format = fConnectedInputs[0]->format;
|
||||||
|
fOutput.format.u.raw_video.display.format = B_NO_COLOR_SPACE;
|
||||||
|
} else {
|
||||||
|
// Second proposal
|
||||||
|
fOutput.format = fConnectedInputs[0]->format;
|
||||||
|
fOutput.format.u.raw_video.display.format = B_RGBA32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*format = fOutput.format;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Presumably we have already agreed with them that this format is
|
||||||
|
// okay. But just in case, we check the offer. (and complain if it
|
||||||
|
// is invalid) Then as the last thing we do, we get rid of any
|
||||||
|
// remaining wilcards.
|
||||||
|
status_t VideoMixerNode::FormatChangeRequested(
|
||||||
|
const media_source &source,
|
||||||
|
const media_destination &destination,
|
||||||
|
media_format *io_format,
|
||||||
|
int32 * _deprecated_)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::FormatChangeRequested\n");
|
||||||
|
|
||||||
|
if (fOutput.source != source) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return B_MEDIA_BAD_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fOutput.destination = destination;
|
||||||
|
fOutput.format = *io_format;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::GetNextOutput( /* cookie starts as 0 */
|
||||||
|
int32 *cookie,
|
||||||
|
media_output *out_output)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::GetNextOutput (%ld)\n",*cookie);
|
||||||
|
|
||||||
|
// only 1 output
|
||||||
|
if (*cookie != 0) {
|
||||||
|
fprintf(stderr,"<- B_ERROR (no more outputs)\n");
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_output = fOutput;
|
||||||
|
*cookie = 1;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::DisposeOutputCookie(int32 cookie)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::DisposeOutputCookie\n");
|
||||||
|
// nothing to do since our cookies are part of the vector iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::SetBufferGroup(
|
||||||
|
const media_source & for_source,
|
||||||
|
BBufferGroup * group)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::SetBufferGroup\n");
|
||||||
|
|
||||||
|
if (fOutput.source != for_source) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return B_MEDIA_BAD_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format of clipping is (as int16-s): <from line> <npairs> <startclip> <endclip>. */
|
||||||
|
/* Repeat for each line where the clipping is different from the previous line. */
|
||||||
|
/* If <npairs> is negative, use the data from line -<npairs> (there are 0 pairs after */
|
||||||
|
/* a negative <npairs>. Yes, we only support 32k*32k frame buffers for clipping. */
|
||||||
|
/* Any non-0 field of 'display' means that that field changed, and if you don't support */
|
||||||
|
/* that change, you should return an error and ignore the request. Note that the buffer */
|
||||||
|
/* offset values do not have wildcards; 0 (or -1, or whatever) are real values and must */
|
||||||
|
/* be adhered to. */
|
||||||
|
status_t VideoMixerNode::VideoClippingChanged(
|
||||||
|
const media_source & for_source,
|
||||||
|
int16 num_shorts,
|
||||||
|
int16 * clip_data,
|
||||||
|
const media_video_display_info & display,
|
||||||
|
int32 * _deprecated_)
|
||||||
|
{
|
||||||
|
return BBufferProducer::VideoClippingChanged(for_source, num_shorts, clip_data, display, _deprecated_);
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::GetLatency(
|
||||||
|
bigtime_t *out_latency)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::GetLatency\n");
|
||||||
|
if (out_latency == NULL) {
|
||||||
|
fprintf(stderr,"<- B_BAD_VALUE\n");
|
||||||
|
return B_BAD_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_latency = EventLatency() + SchedulingLatency();
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::PrepareToConnect(
|
||||||
|
const media_source &what,
|
||||||
|
const media_destination &where,
|
||||||
|
media_format *format,
|
||||||
|
media_source *out_source,
|
||||||
|
char *out_name)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::PrepareToConnect\n");
|
||||||
|
|
||||||
|
if (fOutput.source != what) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return B_MEDIA_BAD_SOURCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out_source = fOutput.source;
|
||||||
|
strcpy(out_name, fOutput.name);
|
||||||
|
|
||||||
|
fOutput.destination = where;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Connect(
|
||||||
|
status_t error,
|
||||||
|
const media_source &source,
|
||||||
|
const media_destination &destination,
|
||||||
|
const media_format &format,
|
||||||
|
char *io_name)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::Connect\n");
|
||||||
|
|
||||||
|
if (fOutput.source != source) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != B_OK) {
|
||||||
|
fprintf(stderr,"<- error already\n");
|
||||||
|
fOutput.destination = media_destination::null;
|
||||||
|
fOutput.format.u.raw_video = media_raw_video_format::wildcard;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the downstream latency
|
||||||
|
// must happen before itr->Connect
|
||||||
|
bigtime_t downstreamLatency;
|
||||||
|
media_node_id id;
|
||||||
|
FindLatencyFor(fOutput.destination, &downstreamLatency, &id);
|
||||||
|
|
||||||
|
// record the agreed upon values
|
||||||
|
fOutput.format = format;
|
||||||
|
fOutput.destination = destination;
|
||||||
|
strcpy(io_name, fOutput.name);
|
||||||
|
|
||||||
|
// compute the internal latency
|
||||||
|
// must happen after itr->Connect
|
||||||
|
if (fInternalLatency == 0) {
|
||||||
|
fInternalLatency = 100; // temporary until we finish computing it
|
||||||
|
ComputeInternalLatency();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the downstream latency for this output is larger
|
||||||
|
// than our current downstream latency, we have to increase
|
||||||
|
// our current downstream latency to be the larger value.
|
||||||
|
if (downstreamLatency > fDownstreamLatency) {
|
||||||
|
SetEventLatency(fDownstreamLatency + fInternalLatency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::ComputeInternalLatency() {
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::ComputeInternalLatency\n");
|
||||||
|
fInternalLatency = 100; // just guess
|
||||||
|
fprintf(stderr," internal latency guessed = %lld\n",fInternalLatency);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::Disconnect(
|
||||||
|
const media_source & what,
|
||||||
|
const media_destination & where)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::Disconnect\n");
|
||||||
|
|
||||||
|
if (fOutput.source != what) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fOutput.destination != where) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fOutput.destination = media_destination::null;
|
||||||
|
GetOutputFormat(&fOutput.format);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::LateNoticeReceived(
|
||||||
|
const media_source & what,
|
||||||
|
bigtime_t how_much,
|
||||||
|
bigtime_t performance_time)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::LateNoticeReceived\n");
|
||||||
|
|
||||||
|
if (fOutput.source != what) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (RunMode()) {
|
||||||
|
case B_OFFLINE:
|
||||||
|
// nothing to do
|
||||||
|
break;
|
||||||
|
case B_RECORDING:
|
||||||
|
// nothing to do
|
||||||
|
break;
|
||||||
|
case B_INCREASE_LATENCY:
|
||||||
|
fInternalLatency += how_much;
|
||||||
|
SetEventLatency(fDownstreamLatency + fInternalLatency);
|
||||||
|
break;
|
||||||
|
case B_DECREASE_PRECISION:
|
||||||
|
// XXX: try to catch up by producing buffers faster
|
||||||
|
break;
|
||||||
|
case B_DROP_DATA:
|
||||||
|
// XXX: should we really drop buffers? just for that output?
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr,"VideoMixerNode::LateNoticeReceived with unexpected run mode.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::EnableOutput(
|
||||||
|
const media_source &what,
|
||||||
|
bool enabled,
|
||||||
|
int32 *_deprecated_)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::EnableOutput\n");
|
||||||
|
|
||||||
|
if (fOutput.source != what) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t status = B_OK;
|
||||||
|
if (status != B_OK) {
|
||||||
|
fprintf(stderr," error in itr->EnableOutput\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status_t VideoMixerNode::SetPlayRate(
|
||||||
|
int32 numer,
|
||||||
|
int32 denom)
|
||||||
|
{
|
||||||
|
BBufferProducer::SetPlayRate(numer, denom); // XXX: do something intelligent later
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::AdditionalBufferRequested( // used to be Reserved 0
|
||||||
|
const media_source & source,
|
||||||
|
media_buffer_id prev_buffer,
|
||||||
|
bigtime_t prev_time,
|
||||||
|
const media_seek_tag * prev_tag)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::AdditionalBufferRequested\n");
|
||||||
|
|
||||||
|
if (fOutput.source != source) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BBuffer * buffer;
|
||||||
|
// status_t status = itr->AdditionalBufferRequested(prev_buffer, prev_time, prev_tag);
|
||||||
|
// if (status != B_OK) {
|
||||||
|
// fprintf(stderr," itr->AdditionalBufferRequested returned an error.\n");
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoMixerNode::LatencyChanged(
|
||||||
|
const media_source & source,
|
||||||
|
const media_destination & destination,
|
||||||
|
bigtime_t new_latency,
|
||||||
|
uint32 flags)
|
||||||
|
{
|
||||||
|
fprintf(stderr,"VideoMixerNode(BBufferProducer)::LatencyChanged\n");
|
||||||
|
|
||||||
|
if (fOutput.source != source) {
|
||||||
|
// we don't have that output
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fOutput.destination != destination) {
|
||||||
|
fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fDownstreamLatency = new_latency;
|
||||||
|
SetEventLatency(fDownstreamLatency + fInternalLatency);
|
||||||
|
|
||||||
|
// XXX: we may have to recompute the number of buffers that we are using
|
||||||
|
// see SetBufferGroup
|
||||||
|
}
|
||||||
@@ -0,0 +1,461 @@
|
|||||||
|
;
|
||||||
|
; Copyright (C) 2009-2010 David McPaul
|
||||||
|
;
|
||||||
|
; All rights reserved. Distributed under the terms of the MIT License.
|
||||||
|
;
|
||||||
|
|
||||||
|
; A rather unoptimised set of yuv to rgb converters
|
||||||
|
; does 8 pixels at a time
|
||||||
|
|
||||||
|
; inputer:
|
||||||
|
; reads 128bits of yuv 8 bit data and puts
|
||||||
|
; the y values converted to 16 bit in xmm0
|
||||||
|
; the u values converted to 16 bit and duplicated into xmm1
|
||||||
|
; the v values converted to 16 bit and duplicated into xmm2
|
||||||
|
|
||||||
|
; conversion:
|
||||||
|
; does the yuv to rgb conversion using 16 bit fixed point and the
|
||||||
|
; results are placed into the following registers as 8 bit clamped values
|
||||||
|
; r values in xmm3
|
||||||
|
; g values in xmm4
|
||||||
|
; b values in xmm5
|
||||||
|
|
||||||
|
; outputer:
|
||||||
|
; writes out the rgba pixels as 8 bit values with 0 for alpha
|
||||||
|
|
||||||
|
; xmm6 used for scratch
|
||||||
|
; xmm7 used for scratch
|
||||||
|
|
||||||
|
%macro cglobal 1
|
||||||
|
global _%1
|
||||||
|
%define %1 _%1
|
||||||
|
align 16
|
||||||
|
%1:
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
; conversion code
|
||||||
|
%macro yuv2rgbsse2 0
|
||||||
|
; u = u - 128
|
||||||
|
; v = v - 128
|
||||||
|
; r = y + v + v >> 2 + v >> 3 + v >> 5
|
||||||
|
; g = y - (u >> 2 + u >> 4 + u >> 5) - (v >> 1 + v >> 3 + v >> 4 + v >> 5)
|
||||||
|
; b = y + u + u >> 1 + u >> 2 + u >> 6
|
||||||
|
; subtract 16 from y
|
||||||
|
movdqa xmm7, [Const16] ; loads a constant using data cache (slower on first fetch but then cached)
|
||||||
|
psubsw xmm0,xmm7 ; y = y - 16
|
||||||
|
; subtract 128 from u and v
|
||||||
|
movdqa xmm7, [Const128] ; loads a constant using data cache (slower on first fetch but then cached)
|
||||||
|
psubsw xmm1,xmm7 ; u = u - 128
|
||||||
|
psubsw xmm2,xmm7 ; v = v - 128
|
||||||
|
; load r,b with y
|
||||||
|
movdqa xmm3,xmm0 ; r = y
|
||||||
|
pshufd xmm5,xmm0, 0xE4 ; b = y
|
||||||
|
|
||||||
|
; r = y + v + v >> 2 + v >> 3 + v >> 5
|
||||||
|
paddsw xmm3, xmm2 ; add v to r
|
||||||
|
movdqa xmm7, xmm1 ; move u to scratch
|
||||||
|
pshufd xmm6, xmm2, 0xE4 ; move v to scratch
|
||||||
|
|
||||||
|
psraw xmm6,2 ; divide v by 4
|
||||||
|
paddsw xmm3, xmm6 ; and add to r
|
||||||
|
psraw xmm6,1 ; divide v by 2
|
||||||
|
paddsw xmm3, xmm6 ; and add to r
|
||||||
|
psraw xmm6,2 ; divide v by 4
|
||||||
|
paddsw xmm3, xmm6 ; and add to r
|
||||||
|
|
||||||
|
; b = y + u + u >> 1 + u >> 2 + u >> 6
|
||||||
|
paddsw xmm5, xmm1 ; add u to b
|
||||||
|
psraw xmm7,1 ; divide u by 2
|
||||||
|
paddsw xmm5, xmm7 ; and add to b
|
||||||
|
psraw xmm7,1 ; divide u by 2
|
||||||
|
paddsw xmm5, xmm7 ; and add to b
|
||||||
|
psraw xmm7,4 ; divide u by 32
|
||||||
|
paddsw xmm5, xmm7 ; and add to b
|
||||||
|
|
||||||
|
; g = y - u >> 2 - u >> 4 - u >> 5 - v >> 1 - v >> 3 - v >> 4 - v >> 5
|
||||||
|
movdqa xmm7,xmm2 ; move v to scratch
|
||||||
|
pshufd xmm6,xmm1, 0xE4 ; move u to scratch
|
||||||
|
movdqa xmm4,xmm0 ; g = y
|
||||||
|
|
||||||
|
psraw xmm6,2 ; divide u by 4
|
||||||
|
psubsw xmm4,xmm6 ; subtract from g
|
||||||
|
psraw xmm6,2 ; divide u by 4
|
||||||
|
psubsw xmm4,xmm6 ; subtract from g
|
||||||
|
psraw xmm6,1 ; divide u by 2
|
||||||
|
psubsw xmm4,xmm6 ; subtract from g
|
||||||
|
|
||||||
|
psraw xmm7,1 ; divide v by 2
|
||||||
|
psubsw xmm4,xmm7 ; subtract from g
|
||||||
|
psraw xmm7,2 ; divide v by 4
|
||||||
|
psubsw xmm4,xmm7 ; subtract from g
|
||||||
|
psraw xmm7,1 ; divide v by 2
|
||||||
|
psubsw xmm4,xmm7 ; subtract from g
|
||||||
|
psraw xmm7,1 ; divide v by 2
|
||||||
|
psubsw xmm4,xmm7 ; subtract from g
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
; conversion code
|
||||||
|
%macro yuv2rgbsse 0
|
||||||
|
; u = u - 128
|
||||||
|
; v = v - 128
|
||||||
|
; r = y + v + v >> 2 + v >> 3 + v >> 5
|
||||||
|
; g = y - (u >> 2 + u >> 4 + u >> 5) - (v >> 1 + v >> 3 + v >> 4 + v >> 5)
|
||||||
|
; b = y + u + u >> 1 + u >> 2 + u >> 6
|
||||||
|
; subtract 16 from y
|
||||||
|
movq mm7, [Const16] ; loads a constant using data cache (slower on first fetch but then cached)
|
||||||
|
psubsw mm0,mm7 ; y = y - 16
|
||||||
|
; subtract 128 from u and v
|
||||||
|
movq mm7, [Const128] ; loads a constant using data cache (slower on first fetch but then cached)
|
||||||
|
psubsw mm1,mm7 ; u = u - 128
|
||||||
|
psubsw mm2,mm7 ; v = v - 128
|
||||||
|
; load r,g,b with y
|
||||||
|
movq mm3,mm0 ; r = y
|
||||||
|
pshufw mm5,mm0, 0xE4 ; b = y
|
||||||
|
|
||||||
|
; r = r + v + v >> 2 + v >> 3 + v >> 5
|
||||||
|
paddsw mm3, mm2 ; add v to r
|
||||||
|
movq mm7, mm1 ; move u to scratch
|
||||||
|
pshufw mm6, mm2, 0xE4 ; move v to scratch
|
||||||
|
|
||||||
|
psraw mm6,2 ; divide v by 4
|
||||||
|
paddsw mm3, mm6 ; and add to r
|
||||||
|
psraw mm6,1 ; divide v by 2
|
||||||
|
paddsw mm3, mm6 ; and add to r
|
||||||
|
psraw mm6,2 ; divide v by 4
|
||||||
|
paddsw mm3, mm6 ; and add to r
|
||||||
|
|
||||||
|
; b = y + u + u >> 1 + u >> 2 + u >> 6
|
||||||
|
paddsw mm5, mm1 ; add u to b
|
||||||
|
psraw mm7,1 ; divide u by 2
|
||||||
|
paddsw mm5, mm7 ; and add to b
|
||||||
|
psraw mm7,1 ; divide u by 2
|
||||||
|
paddsw mm5, mm7 ; and add to b
|
||||||
|
psraw mm7,4 ; divide u by 32
|
||||||
|
paddsw mm5, mm7 ; and add to b
|
||||||
|
|
||||||
|
; g = y - u >> 2 - u >> 4 - u >> 5 - v >> 1 - v >> 3 - v >> 4 - v >> 5
|
||||||
|
movq mm7,mm2 ; move v to scratch
|
||||||
|
pshufw mm6,mm1, 0xE4 ; move u to scratch
|
||||||
|
movq mm4,mm0 ; g = y
|
||||||
|
|
||||||
|
psraw mm6,2 ; divide u by 4
|
||||||
|
psubsw mm4,mm6 ; subtract from g
|
||||||
|
psraw mm6,2 ; divide u by 4
|
||||||
|
psubsw mm4,mm6 ; subtract from g
|
||||||
|
psraw mm6,1 ; divide u by 2
|
||||||
|
psubsw mm4,mm6 ; subtract from g
|
||||||
|
|
||||||
|
psraw mm7,1 ; divide v by 2
|
||||||
|
psubsw mm4,mm7 ; subtract from g
|
||||||
|
psraw mm7,2 ; divide v by 4
|
||||||
|
psubsw mm4,mm7 ; subtract from g
|
||||||
|
psraw mm7,1 ; divide v by 2
|
||||||
|
psubsw mm4,mm7 ; subtract from g
|
||||||
|
psraw mm7,1 ; divide v by 2
|
||||||
|
psubsw mm4,mm7 ; subtract from g
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
; outputer
|
||||||
|
%macro rgba32sse2output 0
|
||||||
|
; clamp values
|
||||||
|
pxor xmm7,xmm7
|
||||||
|
packuswb xmm3,xmm7 ; clamp to 0,255 and pack R to 8 bit per pixel
|
||||||
|
packuswb xmm4,xmm7 ; clamp to 0,255 and pack G to 8 bit per pixel
|
||||||
|
packuswb xmm5,xmm7 ; clamp to 0,255 and pack B to 8 bit per pixel
|
||||||
|
; convert to bgra32 packed
|
||||||
|
punpcklbw xmm5,xmm4 ; bgbgbgbgbgbgbgbg
|
||||||
|
movdqa xmm0, xmm5 ; save bg values
|
||||||
|
punpcklbw xmm3,xmm7 ; r0r0r0r0r0r0r0r0
|
||||||
|
punpcklwd xmm5,xmm3 ; lower half bgr0bgr0bgr0bgr0
|
||||||
|
punpckhwd xmm0,xmm3 ; upper half bgr0bgr0bgr0bgr0
|
||||||
|
; write to output ptr
|
||||||
|
movntdq [edi], xmm5 ; output first 4 pixels bypassing cache
|
||||||
|
movntdq [edi+16], xmm0 ; output second 4 pixels bypassing cache
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
; outputer
|
||||||
|
%macro rgba32sseoutput 0
|
||||||
|
; clamp values
|
||||||
|
pxor mm7,mm7
|
||||||
|
packuswb mm3,mm7 ; clamp to 0,255 and pack R to 8 bit per pixel
|
||||||
|
packuswb mm4,mm7 ; clamp to 0,255 and pack G to 8 bit per pixel
|
||||||
|
packuswb mm5,mm7 ; clamp to 0,255 and pack B to 8 bit per pixel
|
||||||
|
; convert to bgra32 packed
|
||||||
|
punpcklbw mm5,mm4 ; bgbgbgbgbgbgbgbg
|
||||||
|
movq mm0, mm5 ; save bg values
|
||||||
|
punpcklbw mm3,mm7 ; r0r0r0r0
|
||||||
|
punpcklwd mm5,mm3 ; lower half bgr0bgr0
|
||||||
|
punpckhwd mm0,mm3 ; upper half bgr0bgr0
|
||||||
|
; write to output ptr
|
||||||
|
movq [edi], mm5 ; output first 2 pixels
|
||||||
|
movq [edi+8], mm0 ; output second 2 pixels
|
||||||
|
%endmacro
|
||||||
|
|
||||||
|
SECTION .data align=16
|
||||||
|
|
||||||
|
Const16 dw 16
|
||||||
|
dw 16
|
||||||
|
dw 16
|
||||||
|
dw 16
|
||||||
|
dw 16
|
||||||
|
dw 16
|
||||||
|
dw 16
|
||||||
|
dw 16
|
||||||
|
|
||||||
|
Const128 dw 128
|
||||||
|
dw 128
|
||||||
|
dw 128
|
||||||
|
dw 128
|
||||||
|
dw 128
|
||||||
|
dw 128
|
||||||
|
dw 128
|
||||||
|
dw 128
|
||||||
|
|
||||||
|
; void Convert_YUV422_RGBA32_SSE2(void *fromPtr, void *toPtr, int width)
|
||||||
|
width equ ebp+16
|
||||||
|
toPtr equ ebp+12
|
||||||
|
fromPtr equ ebp+8
|
||||||
|
|
||||||
|
; void Convert_YUV420P_RGBA32_SSE2(void *fromYPtr, void *fromUPtr, void *fromVPtr, void *toPtr, int width)
|
||||||
|
width1 equ ebp+24
|
||||||
|
toPtr1 equ ebp+20
|
||||||
|
fromVPtr equ ebp+16
|
||||||
|
fromUPtr equ ebp+12
|
||||||
|
fromYPtr equ ebp+8
|
||||||
|
|
||||||
|
SECTION .text align=16
|
||||||
|
|
||||||
|
cglobal Convert_YUV422_RGBA32_SSE2
|
||||||
|
; reserve variables
|
||||||
|
push ebp
|
||||||
|
mov ebp, esp
|
||||||
|
push edi
|
||||||
|
push esi
|
||||||
|
push ecx
|
||||||
|
|
||||||
|
mov esi, [fromPtr]
|
||||||
|
mov edi, [toPtr]
|
||||||
|
mov ecx, [width]
|
||||||
|
; loop width / 8 times
|
||||||
|
shr ecx,3
|
||||||
|
test ecx,ecx
|
||||||
|
jng ENDLOOP
|
||||||
|
REPEATLOOP: ; loop over width / 8
|
||||||
|
; YUV422 packed inputer
|
||||||
|
movdqa xmm0, [esi] ; should have yuyv yuyv yuyv yuyv
|
||||||
|
pshufd xmm1, xmm0, 0xE4 ; copy to xmm1
|
||||||
|
movdqa xmm2, xmm0 ; copy to xmm2
|
||||||
|
; extract y
|
||||||
|
pxor xmm7,xmm7 ; 00000000000000000000000000000000
|
||||||
|
pcmpeqd xmm6,xmm6 ; ffffffffffffffffffffffffffffffff
|
||||||
|
punpcklbw xmm6,xmm7 ; interleave xmm7 into xmm6 ff00ff00ff00ff00ff00ff00ff00ff00
|
||||||
|
pand xmm0, xmm6 ; clear all but y values leaving y0y0 etc
|
||||||
|
; extract u and duplicate so each u in yuyv becomes 0u0u
|
||||||
|
psrld xmm6,8 ; 00ff0000 00ff0000 00ff0000 00ff0000
|
||||||
|
pand xmm1, xmm6 ; clear all yv values leaving 0u00 etc
|
||||||
|
psrld xmm1,8 ; rotate u to get u000
|
||||||
|
pshuflw xmm1,xmm1, 0xA0 ; copy u values
|
||||||
|
pshufhw xmm1,xmm1, 0xA0 ; to get u0u0
|
||||||
|
; extract v
|
||||||
|
pslld xmm6,16 ; 000000ff000000ff 000000ff000000ff
|
||||||
|
pand xmm2, xmm6 ; clear all yu values leaving 000v etc
|
||||||
|
psrld xmm2,8 ; rotate v to get 00v0
|
||||||
|
pshuflw xmm2,xmm2, 0xF5 ; copy v values
|
||||||
|
pshufhw xmm2,xmm2, 0xF5 ; to get v0v0
|
||||||
|
|
||||||
|
yuv2rgbsse2
|
||||||
|
|
||||||
|
rgba32sse2output
|
||||||
|
|
||||||
|
; endloop
|
||||||
|
add edi,32
|
||||||
|
add esi,16
|
||||||
|
sub ecx, 1 ; apparently sub is better than dec
|
||||||
|
jnz REPEATLOOP
|
||||||
|
ENDLOOP:
|
||||||
|
; Cleanup
|
||||||
|
pop ecx
|
||||||
|
pop esi
|
||||||
|
pop edi
|
||||||
|
mov esp, ebp
|
||||||
|
pop ebp
|
||||||
|
ret
|
||||||
|
|
||||||
|
cglobal Convert_YUV420P_RGBA32_SSE2
|
||||||
|
; reserve variables
|
||||||
|
push ebp
|
||||||
|
mov ebp, esp
|
||||||
|
push edi
|
||||||
|
push esi
|
||||||
|
push ecx
|
||||||
|
push eax
|
||||||
|
push ebx
|
||||||
|
|
||||||
|
mov esi, [fromYPtr]
|
||||||
|
mov eax, [fromUPtr]
|
||||||
|
mov ebx, [fromVPtr]
|
||||||
|
mov edi, [toPtr1]
|
||||||
|
mov ecx, [width1]
|
||||||
|
; loop width / 8 times
|
||||||
|
shr ecx,3
|
||||||
|
test ecx,ecx
|
||||||
|
jng ENDLOOP1
|
||||||
|
REPEATLOOP1: ; loop over width / 8
|
||||||
|
; YUV420 Planar inputer
|
||||||
|
movq xmm0, [esi] ; fetch 8 y values (8 bit) yyyyyyyy00000000
|
||||||
|
movd xmm1, [eax] ; fetch 4 u values (8 bit) uuuu000000000000
|
||||||
|
movd xmm2, [ebx] ; fetch 4 v values (8 bit) vvvv000000000000
|
||||||
|
|
||||||
|
; extract y
|
||||||
|
pxor xmm7,xmm7 ; 00000000000000000000000000000000
|
||||||
|
punpcklbw xmm0,xmm7 ; interleave xmm7 into xmm0 y0y0y0y0y0y0y0y0
|
||||||
|
; extract u and duplicate so each becomes 0u0u
|
||||||
|
punpcklbw xmm1,xmm7 ; interleave xmm7 into xmm1 u0u0u0u000000000
|
||||||
|
punpcklwd xmm1,xmm7 ; interleave again u000u000u000u000
|
||||||
|
pshuflw xmm1,xmm1, 0xA0 ; copy u values
|
||||||
|
pshufhw xmm1,xmm1, 0xA0 ; to get u0u0
|
||||||
|
; extract v
|
||||||
|
punpcklbw xmm2,xmm7 ; interleave xmm7 into xmm1 v0v0v0v000000000
|
||||||
|
punpcklwd xmm2,xmm7 ; interleave again v000v000v000v000
|
||||||
|
pshuflw xmm2,xmm2, 0xA0 ; copy v values
|
||||||
|
pshufhw xmm2,xmm2, 0xA0 ; to get v0v0
|
||||||
|
|
||||||
|
yuv2rgbsse2
|
||||||
|
|
||||||
|
rgba32sse2output
|
||||||
|
|
||||||
|
; endloop
|
||||||
|
add edi,32
|
||||||
|
add esi,8
|
||||||
|
add eax,4
|
||||||
|
add ebx,4
|
||||||
|
sub ecx, 1 ; apparently sub is better than dec
|
||||||
|
jnz REPEATLOOP1
|
||||||
|
ENDLOOP1:
|
||||||
|
; Cleanup
|
||||||
|
pop ebx
|
||||||
|
pop eax
|
||||||
|
pop ecx
|
||||||
|
pop esi
|
||||||
|
pop edi
|
||||||
|
mov esp, ebp
|
||||||
|
pop ebp
|
||||||
|
ret
|
||||||
|
|
||||||
|
cglobal Convert_YUV422_RGBA32_SSE
|
||||||
|
; reserve variables
|
||||||
|
push ebp
|
||||||
|
mov ebp, esp
|
||||||
|
push edi
|
||||||
|
push esi
|
||||||
|
push ecx
|
||||||
|
|
||||||
|
mov esi, [fromPtr]
|
||||||
|
mov ecx, [width]
|
||||||
|
mov edi, [toPtr]
|
||||||
|
; loop width / 4 times
|
||||||
|
shr ecx,2
|
||||||
|
test ecx,ecx
|
||||||
|
jng ENDLOOP2
|
||||||
|
REPEATLOOP2: ; loop over width / 4
|
||||||
|
|
||||||
|
; YUV422 packed inputer
|
||||||
|
movq mm0, [esi] ; should have yuyv yuyv
|
||||||
|
pshufw mm1, mm0, 0xE4 ; copy to mm1
|
||||||
|
movq mm2, mm0 ; copy to mm2
|
||||||
|
; extract y
|
||||||
|
pxor mm7,mm7 ; 0000000000000000
|
||||||
|
pcmpeqb mm6,mm6 ; ffffffffffffffff
|
||||||
|
punpckhbw mm6,mm7 ; interleave mm7 into mm6 ff00ff00ff00ff00
|
||||||
|
pand mm0, mm6 ; clear all but y values leaving y0y0 etc
|
||||||
|
; extract u and duplicate so each u in yuyv becomes 0u0u
|
||||||
|
psrld mm6,8 ; 00ff0000 00ff0000
|
||||||
|
pand mm1, mm6 ; clear all yv values leaving 0u00 etc
|
||||||
|
psrld mm1,8 ; rotate u to get u000
|
||||||
|
pshufw mm1,mm1, 0xA0 ; copy u values to get u0u0 (SSE not MMX)
|
||||||
|
; extract v
|
||||||
|
pslld mm6,16 ; 000000ff000000ff
|
||||||
|
pand mm2, mm6 ; clear all yu values leaving 000v etc
|
||||||
|
psrld mm2,8 ; rotate v to get 00v0
|
||||||
|
pshufw mm2,mm2, 0xF5 ; copy v values to get v0v0 (SSE not MMX)
|
||||||
|
|
||||||
|
yuv2rgbsse
|
||||||
|
|
||||||
|
rgba32sseoutput
|
||||||
|
|
||||||
|
; endloop
|
||||||
|
add edi,16
|
||||||
|
add esi,8
|
||||||
|
sub ecx, 1 ; apparently sub is better than dec
|
||||||
|
jnz REPEATLOOP2
|
||||||
|
ENDLOOP2:
|
||||||
|
; Cleanup
|
||||||
|
emms ; reset mmx regs back to float
|
||||||
|
pop ecx
|
||||||
|
pop esi
|
||||||
|
pop edi
|
||||||
|
mov esp, ebp
|
||||||
|
pop ebp
|
||||||
|
ret
|
||||||
|
|
||||||
|
cglobal Convert_YUV420P_RGBA32_SSE
|
||||||
|
; reserve variables
|
||||||
|
push ebp
|
||||||
|
mov ebp, esp
|
||||||
|
push edi
|
||||||
|
push esi
|
||||||
|
push ecx
|
||||||
|
push eax
|
||||||
|
push ebx
|
||||||
|
|
||||||
|
mov esi, [fromYPtr]
|
||||||
|
mov eax, [fromUPtr]
|
||||||
|
mov ebx, [fromVPtr]
|
||||||
|
mov edi, [toPtr1]
|
||||||
|
mov ecx, [width1]
|
||||||
|
; loop width / 4 times
|
||||||
|
shr ecx,2
|
||||||
|
test ecx,ecx
|
||||||
|
jng ENDLOOP3
|
||||||
|
REPEATLOOP3: ; loop over width / 4
|
||||||
|
; YUV420 Planar inputer
|
||||||
|
movq mm0, [esi] ; fetch 4 y values (8 bit) yyyy0000
|
||||||
|
movd mm1, [eax] ; fetch 2 u values (8 bit) uu000000
|
||||||
|
movd mm2, [ebx] ; fetch 2 v values (8 bit) vv000000
|
||||||
|
|
||||||
|
; extract y
|
||||||
|
pxor mm7,mm7 ; 0000000000000000
|
||||||
|
punpcklbw mm0,mm7 ; interleave xmm7 into xmm0 y0y0y0y
|
||||||
|
; extract u and duplicate so each becomes 0u0u
|
||||||
|
punpcklbw mm1,mm7 ; interleave xmm7 into xmm1 u0u00000
|
||||||
|
punpcklwd mm1,mm7 ; interleave again u000u000
|
||||||
|
pshufw mm1,mm1, 0xA0 ; copy u values to get u0u0
|
||||||
|
; extract v
|
||||||
|
punpcklbw mm2,mm7 ; interleave xmm7 into xmm1 v0v00000
|
||||||
|
punpcklwd mm2,mm7 ; interleave again v000v000
|
||||||
|
pshufw mm2,mm2, 0xA0 ; copy v values to get v0v0
|
||||||
|
|
||||||
|
yuv2rgbsse
|
||||||
|
|
||||||
|
rgba32sseoutput
|
||||||
|
|
||||||
|
; endloop
|
||||||
|
add edi,16
|
||||||
|
add esi,4
|
||||||
|
add eax,2
|
||||||
|
add ebx,2
|
||||||
|
sub ecx, 1 ; apparently sub is better than dec
|
||||||
|
jnz REPEATLOOP3
|
||||||
|
ENDLOOP3:
|
||||||
|
; Cleanup
|
||||||
|
emms
|
||||||
|
pop ebx
|
||||||
|
pop eax
|
||||||
|
pop ecx
|
||||||
|
pop esi
|
||||||
|
pop edi
|
||||||
|
mov esp, ebp
|
||||||
|
pop ebp
|
||||||
|
ret
|
||||||
|
|
||||||
|
SECTION .note.GNU-stack noalloc noexec nowrite progbits
|
||||||
Reference in New Issue
Block a user