From 39ac2af90938aec4f4e43b81a6e043e0ebcdd798 Mon Sep 17 00:00:00 2001 From: dknn Date: Tue, 26 Jun 2012 16:05:35 +0200 Subject: Single threaded codec diff --git a/Swiften/ScreenSharing/Image.cpp b/Swiften/ScreenSharing/Image.cpp new file mode 100644 index 0000000..c3acd69 --- /dev/null +++ b/Swiften/ScreenSharing/Image.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include + +namespace Swift { + +Image::Image(int width, int height, uint8_t* rgb24data) + : width(width), height(height) +{ + int len = width * height * 3; + if (rgb24data) { + data.insert(data.begin(), rgb24data, rgb24data + len); + } else { + data.resize(len); + } +} + +} diff --git a/Swiften/ScreenSharing/Image.h b/Swiften/ScreenSharing/Image.h new file mode 100644 index 0000000..5cdf043 --- /dev/null +++ b/Swiften/ScreenSharing/Image.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include +#include + +namespace Swift { + class Image { + public: + typedef boost::shared_ptr ref; + + Image(int width, int height, uint8_t* rgb24data = 0); + + int width; + int height; + std::vector data; + }; +} diff --git a/Swiften/ScreenSharing/SConscript b/Swiften/ScreenSharing/SConscript index 21b8aa9..3658173 100644 --- a/Swiften/ScreenSharing/SConscript +++ b/Swiften/ScreenSharing/SConscript @@ -1,9 +1,11 @@ Import("swiften_env", "env") sources = [ - "VideoFrame.cpp", - "VideoEncoder.cpp", + "Image.cpp", "VP8Encoder.cpp", + "VP8RTPPacketizer.cpp", + "VP8Decoder.cpp", + "VP8RTPParser.cpp", ] swiften_env.Append(SWIFTEN_OBJECTS = swiften_env.SwiftenObject(sources)) diff --git a/Swiften/ScreenSharing/VP8Decoder.cpp b/Swiften/ScreenSharing/VP8Decoder.cpp new file mode 100644 index 0000000..40f631f --- /dev/null +++ b/Swiften/ScreenSharing/VP8Decoder.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include + +#include +#include +#include "vpx/vp8dx.h" + +namespace Swift { + +inline int clamp8(int v) +{ + return std::min(std::max(v, 0), 255); +} + +VP8Decoder::VP8Decoder() + : VideoDecoder(), + codecInterface(vpx_codec_vp8_dx()), codecFlags(0) +{ + SWIFT_LOG(debug) << "VP8 Decoder:" << vpx_codec_iface_name(codecInterface) << std::endl; + + updateCodecConfig(); +} + +VP8Decoder::~VP8Decoder() +{ +} + +void VP8Decoder::updateCodecConfig() +{ + // TODO: vpx_codec_flags_t flags = VPX_CODEC_USE_INPUT_FRAGMENTS; + vpx_codec_err_t err = vpx_codec_dec_init(&codecContext, codecInterface, NULL, codecFlags); + if (err) { + SWIFT_LOG(debug) << "VP8 Decoder: Failed to initialize decoder, " << vpx_codec_err_to_string(err) << std::endl; + // TODO: exception ? + } +} + +void VP8Decoder::decodeFrame(const std::vector& frame) +{ + // Decode the frame + vpx_codec_err_t err = vpx_codec_decode(&codecContext, &frame[0], frame.size(), NULL, 0); + if (err) { + SWIFT_LOG(debug) << "VP8 Decoder: Failed to decode frame, " << vpx_codec_err_to_string(err) << std::endl; + // TODO: exception ? + return; + } + + vpx_codec_iter_t iter = NULL; + vpx_image_t* img; + while ((img = vpx_codec_get_frame(&codecContext, &iter))) { + Image rgbImg = convertYV12toRGB(img); + onNewImageDecoded(rgbImg); + } +} + +Image VP8Decoder::convertYV12toRGB(const vpx_image_t* img) +{ + Image rgbImg(img->d_w, img->d_h); + std::vector& data = rgbImg.data; + + uint8_t *yPlane = img->planes[VPX_PLANE_Y]; + uint8_t *uPlane = img->planes[VPX_PLANE_U]; + uint8_t *vPlane = img->planes[VPX_PLANE_V]; + + int i = 0; + for (unsigned int imgY = 0; imgY < img->d_h; imgY++) { + for (unsigned int imgX = 0; imgX < img->d_w; imgX++) { + int y = yPlane[imgY * img->stride[VPX_PLANE_Y] + imgX]; + int u = uPlane[(imgY / 2) * img->stride[VPX_PLANE_U] + (imgX / 2)]; + int v = vPlane[(imgY / 2) * img->stride[VPX_PLANE_V] + (imgX / 2)]; + + int c = y - 16; + int d = (u - 128); + int e = (v - 128); + + // TODO: adjust colors ? + + int r = clamp8((298 * c + 409 * e + 128) >> 8); + int g = clamp8((298 * c - 100 * d - 208 * e + 128) >> 8); + int b = clamp8((298 * c + 516 * d + 128) >> 8); + + // TODO: cast instead of clamp8 + + data[i + 0] = static_cast(r); + data[i + 1] = static_cast(g); + data[i + 2] = static_cast(b); + + i += 3; + } + } + return rgbImg; +} + +} diff --git a/Swiften/ScreenSharing/VP8Decoder.h b/Swiften/ScreenSharing/VP8Decoder.h new file mode 100644 index 0000000..8b2575a --- /dev/null +++ b/Swiften/ScreenSharing/VP8Decoder.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include + +#define VPX_CODEC_DISABLE_COMPAT 1 +#include "vpx/vpx_decoder.h" + +namespace Swift { + class Image; + + class VP8Decoder : public VideoDecoder { + public: + VP8Decoder(); + virtual ~VP8Decoder(); + + void updateCodecConfig(); + + virtual void decodeFrame(const std::vector& frame); + + private: + Image convertYV12toRGB(const vpx_image_t* img); + + private: + vpx_codec_iface_t* codecInterface; + vpx_codec_ctx_t codecContext; + vpx_codec_flags_t codecFlags; + }; +} diff --git a/Swiften/ScreenSharing/VP8Encoder.cpp b/Swiften/ScreenSharing/VP8Encoder.cpp index 20c73f9..3ca0edd 100644 --- a/Swiften/ScreenSharing/VP8Encoder.cpp +++ b/Swiften/ScreenSharing/VP8Encoder.cpp @@ -5,13 +5,16 @@ */ #include +#include +#include #include #include "vpx/vp8cx.h" namespace Swift { -VP8Encoder::VP8Encoder(unsigned int width, unsigned int height) : - codecInterface(vpx_codec_vp8_cx()), imageBuffer(0), codecFlags(0), frameFlags(0), frameNumber(0) +VP8Encoder::VP8Encoder(VP8RTPPacketizer* packetizer, unsigned int width, unsigned int height) + : VideoEncoder(), + packetizer(packetizer), codecInterface(vpx_codec_vp8_cx()), imageBuffer(0), codecFlags(0), frameFlags(0), frameNumber(0) { SWIFT_LOG(debug) << "VP8 Encoder:" << vpx_codec_iface_name(codecInterface) << std::endl; @@ -49,6 +52,7 @@ void VP8Encoder::updateCodecConfig() if (err) { SWIFT_LOG(debug) << "VP8 Encoder: Failed to initialize encoder, " << vpx_codec_err_to_string(err) << std::endl; // TODO: exception + return; } imageBuffer = vpx_img_alloc(0, VPX_IMG_FMT_YV12, codecConfig.g_w, codecConfig.g_h, 1); @@ -58,65 +62,63 @@ void VP8Encoder::updateCodecConfig() } } -void VP8Encoder::encodingLoop() +void VP8Encoder::encodeImage(const Image &frame) { - while (!stop) { - VideoFrame::ref frame = popWhenFrameIsAvailable(); - if (stop) - return; - - if (!convertRGB24toYV12inBuffer(frame)) { - SWIFT_LOG(debug) << "VP8 Encoder: Failed to convert frame: Image buffer not initialized" << std::endl; - return; - } + if (!convertRGB24toYV12inBuffer(frame)) { + SWIFT_LOG(debug) << "VP8 Encoder: Failed to convert frame: Image buffer not initialized" << std::endl; + // TODO: exception ? + return; + } - vpx_codec_err_t err = vpx_codec_encode(&codecContext, imageBuffer, frameNumber, 1, frameFlags, VPX_DL_REALTIME); - if (err) { - SWIFT_LOG(debug) << "VP8 Encoder: Failed to encode frame, " << vpx_codec_err_to_string(err) << std::endl; - // TODO: signal ? - return; - } + vpx_codec_err_t err = vpx_codec_encode(&codecContext, imageBuffer, frameNumber, 1, frameFlags, VPX_DL_REALTIME); + if (err) { + SWIFT_LOG(debug) << "VP8 Encoder: Failed to encode frame, " << vpx_codec_err_to_string(err) << std::endl; + // TODO: exception ? + return; + } - const vpx_codec_cx_pkt_t *pkt; - vpx_codec_iter_t iter = NULL; - while ((pkt = vpx_codec_get_cx_data(&codecContext, &iter))) { - switch (pkt->kind) { - case VPX_CODEC_CX_FRAME_PKT: - // TODO: Packetize this frame data - break; - default: - break; - } + const vpx_codec_cx_pkt_t* pkt; + vpx_codec_iter_t iter = NULL; + while ((pkt = vpx_codec_get_cx_data(&codecContext, &iter))) { + switch (pkt->kind) { + case VPX_CODEC_CX_FRAME_PKT: + // TODO: Packetize this frame data + packetizer->packetizeFrame(pkt); + break; + default: + break; } - - ++frameNumber; } + + ++frameNumber; } -bool VP8Encoder::convertRGB24toYV12inBuffer(const VideoFrame::ref frame) +bool VP8Encoder::convertRGB24toYV12inBuffer(const Image &frame) { if (!imageBuffer) return false; - uint8_t *yPlane = imageBuffer->planes[VPX_PLANE_Y]; - uint8_t *uPlane = imageBuffer->planes[VPX_PLANE_U]; - uint8_t *vPlane = imageBuffer->planes[VPX_PLANE_V]; + uint8_t* yPlane = imageBuffer->planes[VPX_PLANE_Y]; + uint8_t* uPlane = imageBuffer->planes[VPX_PLANE_U]; + uint8_t* vPlane = imageBuffer->planes[VPX_PLANE_V]; - unsigned int width = frame->width; - unsigned int height = frame->height; + unsigned int width = frame.width; + unsigned int height = frame.height; unsigned int len = width * height; - const std::vector &data = frame->data; + const std::vector &data = frame.data; for (unsigned int i = 0; i < len; ++i) { - yPlane[i] = data[i * 3]; + const uint8_t* p = &data[3 * i]; + yPlane[i] = ((66 * p[0] + 129 * p[1] + 25 * p[2] + 128) >> 8) + 16; } for (unsigned int line = 0; line < height / 2; ++line) { for (unsigned int col = 0; col < width / 2; ++col) { - const uint8_t *p1 = &data[3 * (2 * (line * width + col))]; - const uint8_t *p2 = &data[3 * (2 * (line * width + col) + 1)]; - const uint8_t *p3 = &data[3 * (2 * (line * width + col) + width)]; - const uint8_t *p4 = &data[3 * (2 * (line * width + col) + width + 1)]; + int pos = 2 * (line * width + col); + const uint8_t* p1 = &data[3 * (pos)]; + const uint8_t* p2 = &data[3 * (pos + 1)]; + const uint8_t* p3 = &data[3 * (pos + width)]; + const uint8_t* p4 = &data[3 * (pos + width + 1)]; int r = (p1[0] + p2[0] + p3[0] + p4[0]) * 0.25; int g = (p1[1] + p2[1] + p3[1] + p4[1]) * 0.25; diff --git a/Swiften/ScreenSharing/VP8Encoder.h b/Swiften/ScreenSharing/VP8Encoder.h index b512058..0a343bb 100644 --- a/Swiften/ScreenSharing/VP8Encoder.h +++ b/Swiften/ScreenSharing/VP8Encoder.h @@ -4,47 +4,39 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#ifndef VP8ENCODER_H -#define VP8ENCODER_H - #pragma once #include -#include - #define VPX_CODEC_DISABLE_COMPAT 1 // Recomended #include "vpx/vpx_encoder.h" namespace Swift { + class Image; + class VP8RTPPacketizer; + class VP8Encoder : public VideoEncoder { public: - VP8Encoder(unsigned int width, unsigned int height); + VP8Encoder(VP8RTPPacketizer* packetizer, unsigned int width, unsigned int height); virtual ~VP8Encoder(); void updateCodecConfig(); - public: - boost::signal onNewFrameEncoded; - - protected: - virtual void encodingLoop(); + virtual void encodeImage(const Image& frame); private: - bool convertRGB24toYV12inBuffer(const VideoFrame::ref frame); + bool convertRGB24toYV12inBuffer(const Image& frame); private: + VP8RTPPacketizer* packetizer; vpx_codec_iface_t* codecInterface; vpx_codec_ctx_t codecContext; vpx_codec_enc_cfg_t codecConfig; - vpx_image_t *imageBuffer; + vpx_image_t* imageBuffer; vpx_codec_flags_t codecFlags; vpx_enc_frame_flags_t frameFlags; int frameNumber; }; } - - -#endif // VP8ENCODER_H diff --git a/Swiften/ScreenSharing/VP8RTPPacketizer.cpp b/Swiften/ScreenSharing/VP8RTPPacketizer.cpp new file mode 100644 index 0000000..4c1e8e9 --- /dev/null +++ b/Swiften/ScreenSharing/VP8RTPPacketizer.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include + + +namespace Swift { + +VP8RTPPacketizer::VP8RTPPacketizer() +{ +} + +void VP8RTPPacketizer::packetizeFrame(const vpx_codec_cx_pkt_t* pkt) +{ + if (pkt->kind != VPX_CODEC_CX_FRAME_PKT) + return; + + // only case there is no partitions is handle (TODO: implement partitions) + if (pkt->data.frame.partition_id != -1) + return; + + size_t size = pkt->data.frame.sz; + uint8_t* buf = static_cast(pkt->data.frame.buf); + size_t sent = 0; + bool firstPacket = true; + + while (sent < size) { + payloadBuffer.clear(); + + // Payload descriptor + uint8_t req = 0; + if (firstPacket) + req |= SBit; + payloadBuffer.push_back(req); + + // Payload header + if (firstPacket) { + firstPacket = false; + + uint8_t o1 = (size & Size0BitMask) << Size0BitShift; // Size0 + o1 |= HBit; // H (show frame) + if (!(pkt->data.frame.flags & VPX_FRAME_IS_KEY)) + o1 |= 1; // P (Inverse key frame) + payloadBuffer.push_back(o1); + payloadBuffer.push_back(static_cast(size >> 3)); // Size1 + payloadBuffer.push_back(static_cast(size >> 11)); // Size2 + } + + // Payload content + size_t toSend = std::min(size - sent, MaxRTPPayloadSize - payloadBuffer.size()); + payloadBuffer.insert(payloadBuffer.end(), buf, buf + toSend); + + sent += toSend; + buf += toSend; + + // Mark rtp packet if last of the curr frame + bool lastPacket = (sent >= size); + + // TODO: send packet + //parser.newPayloadReceived(&payloadBuffer[0], payloadBuffer.size(), lastPacket); + onNewPayloadReady(payloadBuffer, lastPacket); + } +} + +} diff --git a/Swiften/ScreenSharing/VP8RTPPacketizer.h b/Swiften/ScreenSharing/VP8RTPPacketizer.h new file mode 100644 index 0000000..0ef48be --- /dev/null +++ b/Swiften/ScreenSharing/VP8RTPPacketizer.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include +#include +#include +#include "vpx/vpx_encoder.h" + + +namespace Swift { + class SafeByteArray; + + class VP8RTPPacketizer { + public: + VP8RTPPacketizer(); + + void packetizeFrame(const vpx_codec_cx_pkt_t* pkt); + + boost::signal&, bool marker)> onNewPayloadReady; + + private: + static const uint8_t SBit = 1 << 4; + static const uint8_t HBit = 1 << 4; + static const uint8_t Size0BitMask = 7; + static const uint8_t Size0BitShift = 5; + static const size_t MaxRTPPayloadSize = 1400; // Replace with JRTPLIB's one + + std::vector payloadBuffer; + }; +} diff --git a/Swiften/ScreenSharing/VP8RTPParser.cpp b/Swiften/ScreenSharing/VP8RTPParser.cpp new file mode 100644 index 0000000..5a725ea --- /dev/null +++ b/Swiften/ScreenSharing/VP8RTPParser.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include + +#include + +namespace Swift { + +VP8RTPParser::VP8RTPParser(VP8Decoder* decoder) + : decoder(decoder), firstPacket(true) +{ +} + +void VP8RTPParser::newPayloadReceived(const uint8_t* data, size_t len, bool hasMarker) +{ + // Payload descriptor + uint8_t req = data[0]; + if (firstPacket && !(req & SBit)) { /* Error */ } + + size_t headerSize; + // Payload header + if (firstPacket) { + firstPacket = false; + + uint8_t o1 = data[1]; + showFrame = (o1 & 1); + isKeyFrame = (o1 & HBit); + o1 >>= Size0BitShift; + frameSize = o1 + 8 * data[2] + 2048 * data[3]; + + headerSize = 4; + } else { + headerSize = 1; + } + + buffer.insert(buffer.end(), data + headerSize, data + len); + + if (hasMarker || buffer.size() >= frameSize) { + // Frame received + decoder->decodeFrame(buffer); + buffer.clear(); + firstPacket = true; + } +} + +} diff --git a/Swiften/ScreenSharing/VP8RTPParser.h b/Swiften/ScreenSharing/VP8RTPParser.h new file mode 100644 index 0000000..c88e2a6 --- /dev/null +++ b/Swiften/ScreenSharing/VP8RTPParser.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include +#include + +namespace Swift { + class VP8Decoder; + + class VP8RTPParser { + public: + VP8RTPParser(VP8Decoder* decoder); + + void newPayloadReceived(const uint8_t* data, size_t len, bool hasMarker); + + private: + static const uint8_t SBit = 1 << 4; + static const uint8_t HBit = 1 << 4; + static const uint8_t Size0BitShift = 5; + + VP8Decoder* decoder; + + std::vector buffer; + bool firstPacket; + size_t frameSize; + bool isKeyFrame; + bool showFrame; + }; +} diff --git a/Swiften/ScreenSharing/VideoDecoder.h b/Swiften/ScreenSharing/VideoDecoder.h new file mode 100644 index 0000000..810d5ae --- /dev/null +++ b/Swiften/ScreenSharing/VideoDecoder.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2012 Yoann Blein + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include +#include +#include + +namespace Swift { + class Image; + + class VideoDecoder { + public: + VideoDecoder() {} + virtual ~VideoDecoder() {} + + virtual void decodeFrame(const std::vector& frame) = 0; + + public: + boost::signal onNewImageDecoded; + }; +} diff --git a/Swiften/ScreenSharing/VideoEncoder.cpp b/Swiften/ScreenSharing/VideoEncoder.cpp deleted file mode 100644 index 40d9bef..0000000 --- a/Swiften/ScreenSharing/VideoEncoder.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2012 Yoann Blein - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#include - -namespace Swift { - -void VideoEncoder::addFrame(VideoFrame::ref frame) -{ - boost::mutex::scoped_lock lock(queueMutex); - frames.push(frame); - lock.unlock(); - queueCondVar.notify_one(); -} - -void VideoEncoder::startEncoding() -{ - stop = false; - encodingThread = boost::thread(&VideoEncoder::encodingLoop, this); -} - -void VideoEncoder::stopEncoding() -{ - stop = true; - encodingThread.join(); -} - -VideoFrame::ref VideoEncoder::popWhenFrameIsAvailable() -{ - boost::mutex::scoped_lock lock(queueMutex); - while (frames.empty()) { - queueCondVar.wait(lock); - } - VideoFrame::ref popped = frames.front(); - frames.pop(); - return popped; -} - -} diff --git a/Swiften/ScreenSharing/VideoEncoder.h b/Swiften/ScreenSharing/VideoEncoder.h index d98b854..718b59f 100644 --- a/Swiften/ScreenSharing/VideoEncoder.h +++ b/Swiften/ScreenSharing/VideoEncoder.h @@ -4,41 +4,20 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ -#ifndef VIDEOENCODER_H -#define VIDEOENCODER_H +#pragma once -#include -#include -#include -#include #include -#include - +#include namespace Swift { + class Image; + class Packetizer; + class VideoEncoder { public: VideoEncoder() {} virtual ~VideoEncoder() {} - void addFrame(VideoFrame::ref frame); - - void startEncoding(); - void stopEncoding(); - - protected: - virtual void encodingLoop() = 0; - VideoFrame::ref popWhenFrameIsAvailable(); - - protected: - bool stop; - - private: - std::queue frames; - boost::mutex queueMutex; - boost::condition_variable queueCondVar; - boost::thread encodingThread; + virtual void encodeImage(const Image& frame) = 0; }; } - -#endif // VIDEOENCODER_H diff --git a/Swiften/ScreenSharing/VideoFrame.cpp b/Swiften/ScreenSharing/VideoFrame.cpp deleted file mode 100644 index d36ec5c..0000000 --- a/Swiften/ScreenSharing/VideoFrame.cpp +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2012 Yoann Blein - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#include - -namespace Swift { - -VideoFrame::VideoFrame(int width, int height, uint8_t *rgb24data) - : width(width), height(height) -{ - int len = width * height; - data.insert(data.begin(), rgb24data, rgb24data + len); -} - -} diff --git a/Swiften/ScreenSharing/VideoFrame.h b/Swiften/ScreenSharing/VideoFrame.h deleted file mode 100644 index 1d98062..0000000 --- a/Swiften/ScreenSharing/VideoFrame.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2012 Yoann Blein - * Licensed under the simplified BSD license. - * See Documentation/Licenses/BSD-simplified.txt for more information. - */ - -#ifndef VIDEOFRAME_H -#define VIDEOFRAME_H - -#pragma once - -#include -#include -#include - -namespace Swift { - class VideoFrame { - public: - typedef boost::shared_ptr ref; - - VideoFrame(int width, int height, uint8_t *rgb24data); - - int width; - int height; - std::vector data; - }; -} - -#endif // VIDEOFRAME_H -- cgit v0.10.2-6-g49f6