mirror of
https://github.com/niellun/FastCarPlay.git
synced 2026-06-07 09:38:25 +02:00
Optimise queues and rendering
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
||||
#include <condition_variable>
|
||||
|
||||
#include "helper/functions.h"
|
||||
#include "helper/settings.h"
|
||||
#include "settings.h"
|
||||
|
||||
Connector::Connector(uint16_t videoPadding)
|
||||
: _videoPadding(videoPadding)
|
||||
|
||||
+9
-62
@@ -2,7 +2,7 @@
|
||||
|
||||
#include <iostream>
|
||||
#include "helper/functions.h"
|
||||
#include "helper/settings.h"
|
||||
#include "settings.h"
|
||||
|
||||
Decoder::Decoder()
|
||||
{
|
||||
@@ -13,7 +13,7 @@ Decoder::~Decoder()
|
||||
stop();
|
||||
}
|
||||
|
||||
void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
|
||||
void Decoder::start(AtomicQueue<Message> *data, VideoBuffer *vb, AVCodecID codecId)
|
||||
{
|
||||
if (_active)
|
||||
stop();
|
||||
@@ -22,7 +22,6 @@ void Decoder::start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId)
|
||||
_data = data;
|
||||
_codecId = codecId;
|
||||
_active = true;
|
||||
_running = false;
|
||||
_thread = std::thread(&Decoder::runner, this);
|
||||
}
|
||||
void Decoder::stop()
|
||||
@@ -133,11 +132,7 @@ void Decoder::runner()
|
||||
|
||||
void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPacket *packet, AVFrame *frame)
|
||||
{
|
||||
SwsContext *sws = nullptr;
|
||||
int sws_height = 0;
|
||||
int sws_width = 0;
|
||||
uint32_t counter = 0;
|
||||
_running = true;
|
||||
|
||||
std::cout << "Video decoder loop started" << std::endl;
|
||||
|
||||
@@ -145,20 +140,13 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack
|
||||
while (_active)
|
||||
{
|
||||
// Get raw data segment from queue
|
||||
RawEntry segment = _data->wait(_active);
|
||||
std::unique_ptr<Message> segment = _data->wait(_active);
|
||||
|
||||
if (!_active)
|
||||
{
|
||||
if (segment.data)
|
||||
{
|
||||
free(segment.data);
|
||||
segment.data = nullptr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t *data_ptr = segment.data + segment.offset; // Pointer to raw data buffer
|
||||
int data_size = segment.size; // Size of raw data buffer
|
||||
uint8_t *data_ptr = segment->data();
|
||||
int data_size = segment->length();
|
||||
|
||||
// Feed raw data into the parser and decoder
|
||||
while (_active && data_size > 0)
|
||||
@@ -201,52 +189,11 @@ void Decoder::loop(AVCodecContext *context, AVCodecParserContext *parser, AVPack
|
||||
// Receive decoded frames
|
||||
while (avcodec_receive_frame(context, frame) == 0 && _active)
|
||||
{
|
||||
if (!sws || sws_width != frame->width || sws_height != frame->height)
|
||||
{
|
||||
if (sws)
|
||||
{
|
||||
sws_freeContext(sws);
|
||||
sws = nullptr;
|
||||
}
|
||||
|
||||
sws_width = frame->width;
|
||||
sws_height = frame->height;
|
||||
sws = sws_getContext(sws_width, sws_height, (AVPixelFormat)frame->format,
|
||||
_vb->width(), _vb->height(), AV_PIX_FMT_YUV420P,
|
||||
Settings::scaler, nullptr, nullptr, nullptr);
|
||||
if (!sws)
|
||||
{
|
||||
std::cout << "Error creating sws context" << std::endl;
|
||||
_active = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Write decoded frame into video buffer and get output frame pointer
|
||||
const AVFrame *out = _vb->writeFrame(counter++);
|
||||
// Scale frame to output format
|
||||
sws_scale(sws,
|
||||
frame->data, frame->linesize,
|
||||
0, sws_height,
|
||||
out->data,
|
||||
out->linesize);
|
||||
// Commit frame to buffer
|
||||
_vb->commitFrame();
|
||||
AVFrame* out = _vb->write(counter++);
|
||||
av_frame_unref(out);
|
||||
av_frame_move_ref(out, frame);
|
||||
_vb->commit();
|
||||
}
|
||||
}
|
||||
|
||||
if (segment.data)
|
||||
{
|
||||
free(segment.data);
|
||||
segment.data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
_running = false;
|
||||
|
||||
if (sws)
|
||||
{
|
||||
sws_freeContext(sws);
|
||||
sws = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -11,7 +11,9 @@ extern "C"
|
||||
#include <thread>
|
||||
|
||||
#include "struct/video_buffer.h"
|
||||
#include "struct/raw_queue.h"
|
||||
#include "struct/atomic_queue.h"
|
||||
#include "struct/message.h"
|
||||
#include "helper/error.h"
|
||||
|
||||
class Decoder
|
||||
{
|
||||
@@ -20,7 +22,7 @@ public:
|
||||
Decoder();
|
||||
~Decoder();
|
||||
|
||||
void start(RawQueue *data, VideoBuffer *vb, AVCodecID codecId);
|
||||
void start(AtomicQueue<Message> *data, VideoBuffer *vb, AVCodecID codecId);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
@@ -33,9 +35,8 @@ private:
|
||||
Error _status;
|
||||
|
||||
std::atomic<bool> _active = false;
|
||||
std::atomic<bool> _running = false;
|
||||
|
||||
RawQueue *_data = nullptr;
|
||||
AtomicQueue<Message> *_data = nullptr;
|
||||
VideoBuffer *_vb = nullptr;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,4 +21,4 @@ inline void disable_cout()
|
||||
std::cout.setstate(std::ios_base::failbit);
|
||||
}
|
||||
|
||||
#endif /* SRC_HELPER_FUNCTIONS */
|
||||
#endif /* SRC_HELPER_FUNCTIONS */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SRC_PROTOCOL_ISENDER
|
||||
#define SRC_PROTOCOL_ISENDER
|
||||
#ifndef SRC_HELPER_IPROTOCOL
|
||||
#define SRC_HELPER_IPROTOCOL
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
@@ -13,4 +13,4 @@ public:
|
||||
virtual void onStatus(const char* status) = 0;
|
||||
virtual void onDevice(bool connected) = 0;
|
||||
};
|
||||
#endif /* SRC_PROTOCOL_ISENDER */
|
||||
#endif /* SRC_HELPER_IPROTOCOL */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SRC_UX_UFONT
|
||||
#define SRC_UX_UFONT
|
||||
#ifndef SRC_HELPER_UFONT
|
||||
#define SRC_HELPER_UFONT
|
||||
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <iostream>
|
||||
@@ -59,4 +59,4 @@ private:
|
||||
TTF_Font *_font = nullptr;
|
||||
};
|
||||
|
||||
#endif /* SRC_UX_UFONT */
|
||||
#endif /* SRC_HELPER_UFONT */
|
||||
@@ -1,5 +1,5 @@
|
||||
#ifndef SRC_UX_UIMAGE
|
||||
#define SRC_UX_UIMAGE
|
||||
#ifndef SRC_HELPER_UIMAGE
|
||||
#define SRC_HELPER_UIMAGE
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <iostream>
|
||||
@@ -49,4 +49,4 @@ private:
|
||||
SDL_Surface *_surface = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif /* SRC_HELPER_UIMAGE */
|
||||
+30
-59
@@ -1,5 +1,8 @@
|
||||
#include <SDL2/SDL.h> // Include SDL2 library headers for graphics and event handling
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -9,33 +12,21 @@ extern "C"
|
||||
#include <libavutil/imgutils.h> // FFmpeg utility functions for image handling
|
||||
}
|
||||
|
||||
#include <atomic> // C++ atomic types for thread-safe variables
|
||||
#include <mutex> // C++ mutex for locking resources
|
||||
#include <condition_variable> // C++ condition variable for thread synchronization
|
||||
#include <cmath> // Math functions (not explicitly used here but included)
|
||||
#include <cstdio> // Standard C I/O functions
|
||||
#include <vector> // C++ dynamic array container
|
||||
#include <string> // C++ string type
|
||||
#include <unistd.h>
|
||||
#include <iostream>
|
||||
|
||||
#include "resource/background.h"
|
||||
#include "resource/font.h"
|
||||
|
||||
#include "helper/settings.h"
|
||||
#include "helper/functions.h"
|
||||
|
||||
#include "ux/ufont.h"
|
||||
#include "ux/uimage.h"
|
||||
#include "helper/ufont.h"
|
||||
#include "helper/uimage.h"
|
||||
|
||||
#include "struct/video_buffer.h"
|
||||
|
||||
#include "protocol.h"
|
||||
#include "decoder.h"
|
||||
#include "pcm_audio.h"
|
||||
#include "renderer.h"
|
||||
|
||||
static const char *title = "Fast Car Play v0.1";
|
||||
static int width = 0;
|
||||
static int height = 0;
|
||||
static const char *title = "Fast Car Play v0.2";
|
||||
static SDL_Window *window = nullptr;
|
||||
static SDL_Renderer *renderer = nullptr;
|
||||
bool active = false;
|
||||
@@ -43,7 +34,6 @@ bool active = false;
|
||||
static SDL_Texture *textTexture = nullptr;
|
||||
static std::string textureText = "";
|
||||
static SDL_Texture *imgTexture = nullptr;
|
||||
static SDL_Texture *videoTexture = nullptr;
|
||||
|
||||
static bool mouseDown = false;
|
||||
static bool fullscreen = false;
|
||||
@@ -196,10 +186,7 @@ void application()
|
||||
SDL_RenderClear(renderer); // Clear renderer to black
|
||||
SDL_RenderPresent(renderer); // Present initial blank frame
|
||||
|
||||
std::cout << " > Application started" << std::endl;
|
||||
|
||||
VideoBuffer videoBuffer;
|
||||
videoBuffer.allocate(Settings::width, Settings::height).throwError();
|
||||
Protocol protocol(Settings::sourceWidth, Settings::sourceHeight, Settings::sourceFps, AV_INPUT_BUFFER_PADDING_SIZE);
|
||||
Decoder decoder;
|
||||
PcmAudio audio0, audio1, audio2;
|
||||
@@ -221,8 +208,10 @@ void application()
|
||||
SDL_RenderPresent(renderer);
|
||||
|
||||
std::cout << " > Application loop" << std::endl;
|
||||
Renderer videoRenderer(renderer);
|
||||
bool dirty = true;
|
||||
bool connected = false;
|
||||
bool videoPrepared = false;
|
||||
const int activeDelay = 1000 / Settings::fps;
|
||||
const int inactiveDelay = 1000 / 5; // 5FPS
|
||||
uint32_t frameDelay = inactiveDelay;
|
||||
@@ -240,6 +229,7 @@ void application()
|
||||
DrawImage(image);
|
||||
SDL_RenderPresent(renderer);
|
||||
dirty = true;
|
||||
videoPrepared = false;
|
||||
frameDelay = connected ? activeDelay : inactiveDelay;
|
||||
}
|
||||
|
||||
@@ -247,18 +237,18 @@ void application()
|
||||
{
|
||||
AVFrame *frame = nullptr;
|
||||
uint32_t frameid = 0;
|
||||
if (videoBuffer.getLatest(&frame, &frameid) && frameid != latestid)
|
||||
if (videoBuffer.latest(&frame, &frameid) && frameid != latestid && frame)
|
||||
{
|
||||
// Update SDL texture with YUV frame data
|
||||
SDL_UpdateYUVTexture(videoTexture, nullptr,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
if (!videoPrepared)
|
||||
videoPrepared = videoRenderer.prepare(frame, Settings::width, Settings::height, Settings::scaler);
|
||||
if (videoPrepared && videoRenderer.render(frame))
|
||||
{
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, videoRenderer.texture, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
latestid = frameid;
|
||||
videoBuffer.consumeLatest();
|
||||
SDL_RenderClear(renderer);
|
||||
SDL_RenderCopy(renderer, videoTexture, nullptr, nullptr);
|
||||
SDL_RenderPresent(renderer);
|
||||
videoBuffer.consume();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -287,6 +277,7 @@ void application()
|
||||
}
|
||||
}
|
||||
std::cout << " > Application stopping" << std::endl;
|
||||
SDL_HideWindow(window);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -331,23 +322,13 @@ int main(int argc, char **argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Settings::fullscreen)
|
||||
{
|
||||
width = displayMode.w;
|
||||
height = displayMode.h;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = Settings::width;
|
||||
height = Settings::height;
|
||||
}
|
||||
|
||||
// Create SDL window centered on screen, 800x600 size
|
||||
// Create SDL window centered on screen
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
|
||||
window = SDL_CreateWindow(title,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED,
|
||||
width,
|
||||
height,
|
||||
Settings::fullscreen ? displayMode.w : Settings::width,
|
||||
Settings::fullscreen ? displayMode.h : Settings::height,
|
||||
SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN);
|
||||
|
||||
if (!window)
|
||||
@@ -362,19 +343,9 @@ int main(int argc, char **argv)
|
||||
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
|
||||
if (renderer)
|
||||
{
|
||||
videoTexture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_IYUV,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
Settings::width, Settings::height);
|
||||
if (videoTexture)
|
||||
{
|
||||
application();
|
||||
SDL_DestroyTexture(videoTexture);
|
||||
std::cout << " > Application finish" << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "[Main] SDL can't create video texture: " << SDL_GetError() << std::endl;
|
||||
}
|
||||
std::cout << " > Application started" << std::endl;
|
||||
application();
|
||||
std::cout << " > Application finish" << std::endl;
|
||||
SDL_DestroyRenderer(renderer);
|
||||
}
|
||||
else
|
||||
|
||||
+30
-59
@@ -11,12 +11,12 @@ PcmAudio::~PcmAudio()
|
||||
|
||||
void PcmAudio::setVolume(float vol)
|
||||
{
|
||||
if(vol<0)
|
||||
if (vol < 0)
|
||||
{
|
||||
_volume = 0;
|
||||
return;
|
||||
}
|
||||
if(vol>1)
|
||||
if (vol > 1)
|
||||
{
|
||||
_volume = 1;
|
||||
return;
|
||||
@@ -24,7 +24,7 @@ void PcmAudio::setVolume(float vol)
|
||||
_volume = vol;
|
||||
}
|
||||
|
||||
void PcmAudio::start(RawQueue *data)
|
||||
void PcmAudio::start(AtomicQueue<Message> *data)
|
||||
{
|
||||
if (_active)
|
||||
stop();
|
||||
@@ -43,6 +43,21 @@ void PcmAudio::stop()
|
||||
_thread.join();
|
||||
}
|
||||
|
||||
static ChannelConfig configTable[] = {
|
||||
{8000, 1}, // type = 3
|
||||
{48000, 2}, // type = 4
|
||||
{16000, 1}, // type = 5
|
||||
{24000, 1}, // type = 6
|
||||
{16000, 2}, // type = 7
|
||||
};
|
||||
|
||||
ChannelConfig PcmAudio::getConfig(int type) const
|
||||
{
|
||||
if (type >= 3 && type <= 7)
|
||||
return configTable[type - 3];
|
||||
return {44100, 2};
|
||||
}
|
||||
|
||||
void PcmAudio::runner()
|
||||
{
|
||||
setThreadName("audio");
|
||||
@@ -52,63 +67,21 @@ void PcmAudio::runner()
|
||||
size_t bufferedBytes = 0;
|
||||
bool unpaused = false;
|
||||
size_t targetBytes = 0;
|
||||
int rate = 0;
|
||||
int channels = 0;
|
||||
ChannelConfig config = {0, 0};
|
||||
|
||||
while (_active)
|
||||
{
|
||||
RawEntry segment = _data->wait(_active);
|
||||
unique_ptr<Message> segment = _data->wait(_active);
|
||||
if (!_active)
|
||||
{
|
||||
if (segment.data)
|
||||
free(segment.data);
|
||||
break;
|
||||
}
|
||||
|
||||
int config = 0;
|
||||
memcpy(&config, segment.data, sizeof(int));
|
||||
|
||||
int newRate = 44100;
|
||||
int newChannels = 2;
|
||||
switch (config)
|
||||
{
|
||||
case 1:
|
||||
newRate = 44100;
|
||||
newChannels = 2;
|
||||
break;
|
||||
case 2:
|
||||
newRate = 44100;
|
||||
newChannels = 2;
|
||||
break;
|
||||
case 3:
|
||||
newRate = 8000;
|
||||
newChannels = 1;
|
||||
break;
|
||||
case 4:
|
||||
newRate = 48000;
|
||||
newChannels = 2;
|
||||
break;
|
||||
case 5:
|
||||
newRate = 16000;
|
||||
newChannels = 1;
|
||||
break;
|
||||
case 6:
|
||||
newRate = 24000;
|
||||
newChannels = 1;
|
||||
break;
|
||||
case 7:
|
||||
newRate = 16000;
|
||||
newChannels = 2;
|
||||
break;
|
||||
}
|
||||
ChannelConfig segmentConfig = getConfig(segment->getInt(OFFSET_AUDIO_FORMAT));
|
||||
|
||||
// If settings changed, (re)open audio device
|
||||
if (device == 0 || rate != newRate || channels != newChannels)
|
||||
if (device == 0 || config != segmentConfig)
|
||||
{
|
||||
rate = newRate;
|
||||
channels = newChannels;
|
||||
config = segmentConfig;
|
||||
|
||||
printf("PCM SETTING %d %d\n", rate, channels);
|
||||
// Close existing device
|
||||
if (device != 0)
|
||||
{
|
||||
@@ -117,9 +90,9 @@ void PcmAudio::runner()
|
||||
}
|
||||
// Configure new spec
|
||||
SDL_zero(spec);
|
||||
spec.freq = rate;
|
||||
spec.freq = config.rate;
|
||||
spec.format = AUDIO_S16SYS;
|
||||
spec.channels = static_cast<Uint8>(channels);
|
||||
spec.channels = config.channels;
|
||||
spec.samples = 4096;
|
||||
spec.callback = nullptr;
|
||||
|
||||
@@ -127,11 +100,10 @@ void PcmAudio::runner()
|
||||
if (device == 0)
|
||||
{
|
||||
std::cerr << "Failed to open audio: " << SDL_GetError() << std::endl;
|
||||
free(segment.data);
|
||||
continue;
|
||||
}
|
||||
// Calculate new buffer target: 0.5s
|
||||
targetBytes = rate * channels * sizeof(int16_t) / 2;
|
||||
targetBytes = config.rate * config.channels * sizeof(int16_t) / 2;
|
||||
bufferedBytes = 0;
|
||||
unpaused = false;
|
||||
// Start paused
|
||||
@@ -139,17 +111,16 @@ void PcmAudio::runner()
|
||||
}
|
||||
|
||||
// Apply volume in-place
|
||||
int16_t *samples = reinterpret_cast<int16_t *>(segment.data + segment.offset);
|
||||
size_t count = segment.size / sizeof(int16_t);
|
||||
int16_t *samples = reinterpret_cast<int16_t *>(segment->data());
|
||||
size_t count = segment->length() / sizeof(int16_t);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
samples[i] = static_cast<int16_t>(samples[i] * _volume);
|
||||
}
|
||||
|
||||
// Queue audio
|
||||
SDL_QueueAudio(device, segment.data + segment.offset, segment.size);
|
||||
bufferedBytes += segment.size;
|
||||
free(segment.data);
|
||||
SDL_QueueAudio(device, segment->data(), segment->length());
|
||||
bufferedBytes += segment->length();
|
||||
|
||||
// Unpause when enough buffered
|
||||
if (!unpaused && bufferedBytes >= targetBytes)
|
||||
|
||||
+22
-6
@@ -7,28 +7,44 @@
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
#include "struct/raw_queue.h"
|
||||
#include "struct/atomic_queue.h"
|
||||
#include "struct/message.h"
|
||||
#include "helper/error.h"
|
||||
|
||||
struct ChannelConfig
|
||||
{
|
||||
int rate;
|
||||
uint8_t channels;
|
||||
|
||||
class PcmAudio {
|
||||
bool operator==(ChannelConfig const &other) const
|
||||
{
|
||||
return rate == other.rate && channels == other.channels;
|
||||
}
|
||||
|
||||
bool operator!=(ChannelConfig const &other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
};
|
||||
|
||||
class PcmAudio
|
||||
{
|
||||
public:
|
||||
PcmAudio();
|
||||
~PcmAudio();
|
||||
|
||||
// Start playing raw PCM data from queue
|
||||
void start(RawQueue* data);
|
||||
void start(AtomicQueue<Message> *data);
|
||||
void stop();
|
||||
|
||||
void setVolume(float vol);
|
||||
|
||||
private:
|
||||
ChannelConfig getConfig(int type) const;
|
||||
void runner();
|
||||
void loop(SDL_AudioDeviceID device);
|
||||
|
||||
RawQueue* _data = nullptr;
|
||||
int _sampleRate = 0;
|
||||
int _channels = 0;
|
||||
AtomicQueue<Message> *_data = nullptr;
|
||||
|
||||
float _volume = 1.0f;
|
||||
|
||||
|
||||
+4
-4
@@ -178,7 +178,7 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
|
||||
{
|
||||
if (length <= 20)
|
||||
break;
|
||||
videoData.push(data, 20, length - 20);
|
||||
videoData.pushDiscard( std::make_unique<Message>(data, length, 20));
|
||||
dispose = false;
|
||||
break;
|
||||
}
|
||||
@@ -193,19 +193,19 @@ void Protocol::onData(uint32_t cmd, uint32_t length, uint8_t *data)
|
||||
memcpy(&channel, data + 8, sizeof(int));
|
||||
if (channel == 0)
|
||||
{
|
||||
audioStream0.push(data, 12, length - 12);
|
||||
audioStream0.pushDiscard(std::make_unique<Message>(data, length, 12));
|
||||
dispose = false;
|
||||
break;
|
||||
}
|
||||
if (channel == 1)
|
||||
{
|
||||
audioStream1.push(data, 12, length - 12);
|
||||
audioStream1.pushDiscard(std::make_unique<Message>(data, length, 12));
|
||||
dispose = false;
|
||||
break;
|
||||
}
|
||||
if (channel == 2)
|
||||
{
|
||||
audioStream2.push(data, 12, length - 12);
|
||||
audioStream2.pushDiscard(std::make_unique<Message>(data, length, 12));
|
||||
dispose = false;
|
||||
break;
|
||||
}
|
||||
|
||||
+7
-6
@@ -1,9 +1,10 @@
|
||||
#ifndef SRC_PROTOCOL
|
||||
#define SRC_PROTOCOL
|
||||
|
||||
#include "struct/raw_queue.h"
|
||||
#include "struct/atomic_queue.h"
|
||||
#include "struct/message.h"
|
||||
#include "helper/iprotocol.h"
|
||||
#include "helper/settings.h"
|
||||
#include "settings.h"
|
||||
#include "connector.h"
|
||||
|
||||
#define MAGIC 0x55aa55aa
|
||||
@@ -40,10 +41,10 @@ public:
|
||||
void sendMove(float dx, float dy);
|
||||
|
||||
Connector connector;
|
||||
RawQueue videoData;
|
||||
RawQueue audioStream0;
|
||||
RawQueue audioStream1;
|
||||
RawQueue audioStream2;
|
||||
AtomicQueue<Message> videoData;
|
||||
AtomicQueue<Message> audioStream0;
|
||||
AtomicQueue<Message> audioStream1;
|
||||
AtomicQueue<Message> audioStream2;
|
||||
bool phoneConnected;
|
||||
|
||||
private:
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
#include "renderer.h"
|
||||
#include "helper/error.h"
|
||||
#include <iostream>
|
||||
|
||||
const Renderer::FormatMapping Renderer::_mapping[] = {
|
||||
{AV_PIX_FMT_RGB24, SDL_PIXELFORMAT_RGB24, &Renderer::rgb, "RGB24"},
|
||||
{AV_PIX_FMT_YUV420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUV420P"},
|
||||
{AV_PIX_FMT_YUVJ420P, SDL_PIXELFORMAT_IYUV, &Renderer::yuv, "YUVJ420P"},
|
||||
{AV_PIX_FMT_NV12, SDL_PIXELFORMAT_NV12, &Renderer::nv, "NV12"}};
|
||||
|
||||
Renderer::Renderer(SDL_Renderer *renderer)
|
||||
: texture(nullptr),
|
||||
textureWidth(0),
|
||||
textureHeight(0),
|
||||
_renderer(renderer),
|
||||
_render(nullptr),
|
||||
_sws(nullptr),
|
||||
_swsWidth(0),
|
||||
_swsHeight(0),
|
||||
_frame(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Renderer::~Renderer()
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
SDL_DestroyTexture(texture);
|
||||
texture = nullptr;
|
||||
}
|
||||
if (_sws)
|
||||
{
|
||||
sws_freeContext(_sws);
|
||||
_sws = nullptr;
|
||||
}
|
||||
if (_frame)
|
||||
{
|
||||
av_frame_free(&_frame);
|
||||
_frame = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Renderer::render(AVFrame *frame)
|
||||
{
|
||||
if (_render == nullptr)
|
||||
return false;
|
||||
(this->*_render)(frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Renderer::prepareTexture(uint32_t format, int width, int height)
|
||||
{
|
||||
if (texture)
|
||||
{
|
||||
if (textureWidth == width && textureHeight == height)
|
||||
return true;
|
||||
SDL_DestroyTexture(texture);
|
||||
texture = nullptr;
|
||||
}
|
||||
|
||||
texture = SDL_CreateTexture(_renderer, format,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
width, height);
|
||||
if (!texture)
|
||||
{
|
||||
std::cerr << "[Render] SDL can't create video texture: " << SDL_GetError() << std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Renderer::prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler)
|
||||
{
|
||||
if (frame->width == targetWidth && frame->height == targetHeight)
|
||||
{
|
||||
AVPixelFormat fmt = static_cast<AVPixelFormat>(frame->format);
|
||||
for (const FormatMapping &mapping : _mapping)
|
||||
{
|
||||
if (mapping.avFormat == fmt)
|
||||
{
|
||||
if (prepareTexture(mapping.sdlFormat, targetWidth, targetHeight))
|
||||
{
|
||||
std::cout << "[Render] Direct rendering " << mapping.name << std::endl;
|
||||
_render = mapping.function;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "[Render] Scaling required from " << frame->width << "x" << frame->height << " to " << targetWidth << "x" << targetHeight << std::endl;
|
||||
}
|
||||
|
||||
if (!prepareTexture(SDL_PIXELFORMAT_IYUV, targetWidth, targetHeight))
|
||||
return false;
|
||||
|
||||
if (!_sws || _swsWidth != frame->width || _swsHeight != frame->height)
|
||||
{
|
||||
if (_sws)
|
||||
{
|
||||
sws_freeContext(_sws);
|
||||
_sws = nullptr;
|
||||
}
|
||||
|
||||
_sws = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format,
|
||||
targetWidth, targetHeight, AV_PIX_FMT_YUV420P,
|
||||
scaler, nullptr, nullptr, nullptr);
|
||||
if (!_sws)
|
||||
{
|
||||
std::cerr << "[Render] Can't create sws context" << std::endl;
|
||||
return false;
|
||||
}
|
||||
_swsWidth = frame->width;
|
||||
_swsHeight = frame->height;
|
||||
}
|
||||
|
||||
if (!_frame)
|
||||
{
|
||||
_frame = av_frame_alloc();
|
||||
if (!_frame)
|
||||
{
|
||||
std::cerr << "[Render] Can't allocate AVFrame" << std::endl;
|
||||
return false;
|
||||
}
|
||||
_frame->format = AV_PIX_FMT_YUV420P;
|
||||
_frame->width = targetWidth;
|
||||
_frame->height = targetHeight;
|
||||
// Allocate data buffer with 32 byte allingment
|
||||
int avRes = av_frame_get_buffer(_frame, 32);
|
||||
if (avRes != 0)
|
||||
{
|
||||
std::cerr << "[Render] Can't allocate AVFrame buffer: " << Error::avErrorText(avRes) << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "[Render] Scaling rendering source format " << frame->format << std::endl;
|
||||
_render = &Renderer::scale;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::rgb(AVFrame *frame)
|
||||
{
|
||||
SDL_UpdateTexture(
|
||||
texture,
|
||||
nullptr,
|
||||
frame->data[0], frame->linesize[0]);
|
||||
}
|
||||
|
||||
void Renderer::nv(AVFrame *frame)
|
||||
{
|
||||
SDL_UpdateNVTexture(
|
||||
texture,
|
||||
nullptr,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1]);
|
||||
}
|
||||
void Renderer::yuv(AVFrame *frame)
|
||||
{
|
||||
SDL_UpdateYUVTexture(
|
||||
texture,
|
||||
nullptr,
|
||||
frame->data[0], frame->linesize[0],
|
||||
frame->data[1], frame->linesize[1],
|
||||
frame->data[2], frame->linesize[2]);
|
||||
}
|
||||
|
||||
void Renderer::scale(AVFrame *frame)
|
||||
{
|
||||
// Scale frame to output format
|
||||
sws_scale(_sws,
|
||||
frame->data, frame->linesize,
|
||||
0, _swsHeight,
|
||||
_frame->data,
|
||||
_frame->linesize);
|
||||
|
||||
// Update SDL texture with YUV frame data
|
||||
SDL_UpdateYUVTexture(texture, nullptr,
|
||||
_frame->data[0], _frame->linesize[0],
|
||||
_frame->data[1], _frame->linesize[1],
|
||||
_frame->data[2], _frame->linesize[2]);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef SRC_RENDERER
|
||||
#define SRC_RENDERER
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavformat/avformat.h> // FFmpeg library for multimedia container format handling
|
||||
#include <libswscale/swscale.h> // FFmpeg library for image scaling and pixel format conversion
|
||||
}
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <string>
|
||||
|
||||
class Renderer
|
||||
{
|
||||
public:
|
||||
Renderer(SDL_Renderer *renderer);
|
||||
~Renderer();
|
||||
|
||||
bool prepare(AVFrame *frame, int targetWidth, int targetHeight, uint32_t scaler);
|
||||
bool render(AVFrame *frame);
|
||||
|
||||
SDL_Texture *texture;
|
||||
int textureWidth;
|
||||
int textureHeight;
|
||||
|
||||
private:
|
||||
using DrawFuncType = void (Renderer::*)(AVFrame *);
|
||||
|
||||
struct FormatMapping
|
||||
{
|
||||
AVPixelFormat avFormat;
|
||||
SDL_PixelFormatEnum sdlFormat;
|
||||
DrawFuncType function;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
bool prepareTexture(uint32_t format, int width, int height);
|
||||
|
||||
void rgb(AVFrame *frame);
|
||||
void nv(AVFrame *frame);
|
||||
void yuv(AVFrame *frame);
|
||||
void scale(AVFrame *frame);
|
||||
|
||||
SDL_Renderer *_renderer;
|
||||
DrawFuncType _render;
|
||||
SwsContext *_sws;
|
||||
int _swsWidth;
|
||||
int _swsHeight;
|
||||
AVFrame *_frame;
|
||||
static const FormatMapping _mapping[];
|
||||
};
|
||||
|
||||
#endif /* SRC_RENDERER */
|
||||
@@ -1,7 +1,7 @@
|
||||
#ifndef SRC_HELPER_SETTINGS
|
||||
#define SRC_HELPER_SETTINGS
|
||||
#ifndef SRC_SETTINGS
|
||||
#define SRC_SETTINGS
|
||||
|
||||
#include "settings_base.h"
|
||||
#include "helper/settings_base.h"
|
||||
|
||||
// The singleton “Settings” namespace
|
||||
class Settings
|
||||
@@ -9,14 +9,14 @@ class Settings
|
||||
public:
|
||||
static inline Setting<int> vendorid{"vendor-id", 4884};
|
||||
static inline Setting<int> productid{"product-id ", 5408};
|
||||
static inline Setting<bool> fullscreen{"fullscreen", false};
|
||||
static inline Setting<bool> fullscreen{"fullscreen", true};
|
||||
static inline Setting<int> width{"width", 720};
|
||||
static inline Setting<int> height{"height", 576};
|
||||
static inline Setting<int> fps{"fps", 60};
|
||||
static inline Setting<int> sourceWidth{"source-width", 720};
|
||||
static inline Setting<int> sourceHeight{"source-height", 576};
|
||||
static inline Setting<int> sourceFps{"source-fps", 30};
|
||||
static inline Setting<bool> logging{"logging", true};
|
||||
static inline Setting<bool> logging{"logging", false};
|
||||
static inline Setting<int> scaler{"scaler", 2};
|
||||
static inline Setting<int> queue{"queue-size", 32};
|
||||
static inline Setting<int> fontSize{"font-size", 30};
|
||||
@@ -28,4 +28,4 @@ private:
|
||||
static void trim(std::string &s);
|
||||
};
|
||||
|
||||
#endif /* SRC_HELPER_SETTINGS */
|
||||
#endif /* SRC_SETTINGS */
|
||||
@@ -0,0 +1,105 @@
|
||||
#ifndef SRC_STRUCT_ATOMIC_QUEUE
|
||||
#define SRC_STRUCT_ATOMIC_QUEUE
|
||||
|
||||
#include <cstdint>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
template <typename T>
|
||||
class AtomicQueue
|
||||
{
|
||||
public:
|
||||
AtomicQueue(uint16_t size)
|
||||
: _size(size), _data(new unique_ptr<T>[size]), _first(0), _last(0), _count(0)
|
||||
{
|
||||
}
|
||||
|
||||
AtomicQueue(const AtomicQueue &) = delete;
|
||||
AtomicQueue &operator=(const AtomicQueue &) = delete;
|
||||
|
||||
~AtomicQueue() = default;
|
||||
|
||||
bool pushDiscard(unique_ptr<T> obj)
|
||||
{
|
||||
if (_count == _size)
|
||||
return false;
|
||||
|
||||
_first = (_first + 1) % _size;
|
||||
_data[_first] = std::move(obj);
|
||||
++_count;
|
||||
_lock.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pushReplace(unique_ptr<T> obj)
|
||||
{
|
||||
if (_count == _size)
|
||||
{
|
||||
_data[_first] = std::move(obj);
|
||||
return false;
|
||||
}
|
||||
|
||||
_first = (_first + 1) % _size;
|
||||
_data[_first] = std::move(obj);
|
||||
++_count;
|
||||
_lock.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
unique_ptr<T> pop()
|
||||
{
|
||||
if (_count == 0)
|
||||
return nullptr;
|
||||
|
||||
_last = (_last + 1) % _size;
|
||||
auto item = std::move(_data[_last]);
|
||||
--_count;
|
||||
return item;
|
||||
}
|
||||
|
||||
unique_ptr<T> wait(atomic<bool> &waitFlag)
|
||||
{
|
||||
unique_lock<std::mutex> lock(_mtx);
|
||||
|
||||
_lock.wait(lock, [&]
|
||||
{ return _count > 0 || !waitFlag; });
|
||||
|
||||
if (!waitFlag)
|
||||
return nullptr;
|
||||
|
||||
_last = (_last + 1) % _size;
|
||||
auto item = std::move(_data[_last]);
|
||||
--_count;
|
||||
return item;
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
_data = std::make_unique<std::unique_ptr<T>[]>(_size);
|
||||
_first = 0;
|
||||
_last = 0;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
void notify()
|
||||
{
|
||||
_lock.notify_all();
|
||||
}
|
||||
|
||||
uint16_t count() { return _count; }
|
||||
|
||||
private:
|
||||
uint16_t _size;
|
||||
unique_ptr<unique_ptr<T>[]> _data;
|
||||
uint16_t _first;
|
||||
uint16_t _last;
|
||||
atomic<uint16_t> _count;
|
||||
mutex _mtx;
|
||||
condition_variable _lock;
|
||||
};
|
||||
|
||||
#endif /* SRC_STRUCT_ATOMIC_QUEUE */
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef SRC_STRUCT_MESSAGE
|
||||
#define SRC_STRUCT_MESSAGE
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#define OFFSET_AUDIO_FORMAT 0
|
||||
|
||||
class Message
|
||||
{
|
||||
public:
|
||||
Message(uint8_t *data, uint32_t data_length, uint32_t offset) : _data(data), _length(data_length), _offset(offset)
|
||||
{
|
||||
}
|
||||
|
||||
~Message()
|
||||
{
|
||||
if (_data)
|
||||
{
|
||||
free(_data);
|
||||
_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int getInt(uint32_t offset) const
|
||||
{
|
||||
int result = 0;
|
||||
if (_length - sizeof(int) >= offset)
|
||||
memcpy(&result, _data + offset, sizeof(int));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t *data() const { return _data + _offset; }
|
||||
uint32_t length() const { return _length - _offset; }
|
||||
|
||||
private:
|
||||
uint8_t *_data;
|
||||
uint32_t _length;
|
||||
uint32_t _offset;
|
||||
};
|
||||
|
||||
#endif /* SRC_STRUCT_MESSAGE */
|
||||
@@ -1,69 +0,0 @@
|
||||
#include "raw_queue.h"
|
||||
|
||||
RawQueue::RawQueue(uint16_t capacity)
|
||||
: _buffer(capacity), _head(0), _tail(0), _size(0), _capacity(capacity)
|
||||
{
|
||||
}
|
||||
|
||||
RawQueue::~RawQueue()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
bool RawQueue::push(uint8_t *data, int offset, int size)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_size == _buffer.size())
|
||||
{
|
||||
free(data);
|
||||
return false; // queue full
|
||||
}
|
||||
|
||||
_buffer[_tail] = RawEntry{data, offset, size};
|
||||
_tail = (_tail + 1) % _capacity;
|
||||
_size++;
|
||||
|
||||
_condition.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
RawEntry RawQueue::wait(const std::atomic<bool> &reading)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_condition.wait(lock, [&]
|
||||
{ return !reading.load() || _size > 0; });
|
||||
|
||||
if (!reading || _size == 0)
|
||||
return RawEntry{nullptr, 0, 0};
|
||||
|
||||
RawEntry entry = _buffer[_head];
|
||||
_head = (_head + 1) % _capacity;
|
||||
_size--;
|
||||
return entry;
|
||||
}
|
||||
|
||||
void RawQueue::clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
|
||||
// Free any remaining buffers
|
||||
while (_size > 0)
|
||||
{
|
||||
RawEntry &e = _buffer[_head];
|
||||
if (e.data)
|
||||
{
|
||||
free(e.data);
|
||||
e.data = nullptr;
|
||||
}
|
||||
_head = (_head + 1) % _capacity;
|
||||
_size--;
|
||||
}
|
||||
|
||||
// Reset indices
|
||||
_head = _tail = 0;
|
||||
}
|
||||
|
||||
void RawQueue::notify()
|
||||
{
|
||||
_condition.notify_all();
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef SRC_RAW_QUEUE
|
||||
#define SRC_RAW_QUEUE
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
struct RawEntry
|
||||
{
|
||||
uint8_t *data;
|
||||
int offset;
|
||||
int size;
|
||||
};
|
||||
|
||||
// Single entry: raw buffer pointer + metadata
|
||||
class RawQueue
|
||||
{
|
||||
public:
|
||||
RawQueue(uint16_t capacity = 256);
|
||||
~RawQueue();
|
||||
|
||||
// Non-blocking push: returns false if full
|
||||
bool push(uint8_t *data, int offset, int size);
|
||||
|
||||
// Blocks until an entry is available or reader_active == false
|
||||
RawEntry wait(const std::atomic<bool> &reading);
|
||||
|
||||
// Clears the queue and frees any pending buffers
|
||||
void clear();
|
||||
|
||||
// Unlock queus
|
||||
void notify();
|
||||
|
||||
private:
|
||||
std::vector<RawEntry> _buffer;
|
||||
uint16_t _head;
|
||||
uint16_t _tail;
|
||||
uint16_t _size;
|
||||
uint16_t _capacity;
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _condition;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,111 +0,0 @@
|
||||
#include "video_buffer.h"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavutil/imgutils.h>
|
||||
}
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
VideoBuffer::VideoBuffer()
|
||||
: _width(0), _height(0)
|
||||
{
|
||||
}
|
||||
|
||||
// Allocate two YUV420P frames for double buffering and initialize to black
|
||||
Error VideoBuffer::allocate(uint16_t width, uint16_t height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
deallocate();
|
||||
reset();
|
||||
|
||||
Error e;
|
||||
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
|
||||
{
|
||||
// Allocate AVFrame
|
||||
_frames[i] = av_frame_alloc();
|
||||
if (e.null(_frames[i], "Failed to allocate AVFrame"))
|
||||
break;
|
||||
_frames[i]->format = AV_PIX_FMT_YUV420P;
|
||||
_frames[i]->width = width;
|
||||
_frames[i]->height = height;
|
||||
// Allocate data buffer with 32 byte allingment
|
||||
if (e.avFail(av_frame_get_buffer(_frames[i], 32), "Failed to allocate AVFrame buffer"))
|
||||
break;
|
||||
// Set Y plane to black (0)
|
||||
memset(_frames[i]->data[0], 0, _frames[i]->linesize[0] * height);
|
||||
// Set U plane to 128 (neutral)
|
||||
memset(_frames[i]->data[1], 128, _frames[i]->linesize[1] * (height / 2));
|
||||
// Set V plane to 128 (neutral)
|
||||
memset(_frames[i]->data[2], 128, _frames[i]->linesize[2] * (height / 2));
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
VideoBuffer::~VideoBuffer()
|
||||
{
|
||||
deallocate();
|
||||
}
|
||||
|
||||
void VideoBuffer::deallocate()
|
||||
{
|
||||
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
|
||||
{
|
||||
if (_frames[i])
|
||||
{
|
||||
// Free the frame itself
|
||||
av_frame_free(&_frames[i]);
|
||||
// Clear
|
||||
_frames[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VideoBuffer::getLatest(AVFrame **frame, uint32_t *id)
|
||||
{
|
||||
_reading.store(_latest.load());
|
||||
int index = _reading.load();
|
||||
if (index < 0)
|
||||
return false;
|
||||
*frame = _frames[index];
|
||||
*id = _ids[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoBuffer::consumeLatest()
|
||||
{
|
||||
_reading.store(-1);
|
||||
}
|
||||
|
||||
const AVFrame *VideoBuffer::writeFrame(uint32_t id)
|
||||
{
|
||||
int index = _writing.load();
|
||||
while (index == _reading.load() || index == _latest.load())
|
||||
{
|
||||
index = (index + 1) % BUFFER_VIDEO_FRAMES;
|
||||
}
|
||||
_writing.store(index);
|
||||
_ids[index] = id;
|
||||
return _frames[index];
|
||||
}
|
||||
|
||||
void VideoBuffer::commitFrame()
|
||||
{
|
||||
_latest.store(_writing.load());
|
||||
}
|
||||
|
||||
void VideoBuffer::reset()
|
||||
{
|
||||
_writing.store(0);
|
||||
_reading.store(-1);
|
||||
_latest.store(-1);
|
||||
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; i++)
|
||||
{
|
||||
_ids[i] = 0;
|
||||
}
|
||||
}
|
||||
+64
-20
@@ -1,5 +1,5 @@
|
||||
#ifndef SRC_VIDEO_BUFFER
|
||||
#define SRC_VIDEO_BUFFER
|
||||
#ifndef SRC_STRUCT_VIDEO_BUFFER
|
||||
#define SRC_STRUCT_VIDEO_BUFFER
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@@ -7,31 +7,75 @@ extern "C"
|
||||
}
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "helper/error.h"
|
||||
#include <stdexcept>
|
||||
|
||||
#define BUFFER_VIDEO_FRAMES 3
|
||||
|
||||
class VideoBuffer
|
||||
{
|
||||
public:
|
||||
VideoBuffer();
|
||||
~VideoBuffer();
|
||||
|
||||
Error allocate(uint16_t width, uint16_t height);
|
||||
uint16_t width() const { return _width; };
|
||||
uint16_t height() const { return _height; };
|
||||
void reset();
|
||||
bool getLatest(AVFrame **frame, uint32_t *id);
|
||||
void consumeLatest();
|
||||
const AVFrame *writeFrame(uint32_t id);
|
||||
void commitFrame();
|
||||
VideoBuffer()
|
||||
{
|
||||
_writing.store(0);
|
||||
_reading.store(-1);
|
||||
_latest.store(-1);
|
||||
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
|
||||
{
|
||||
_ids[i] = 0;
|
||||
_frames[i] = av_frame_alloc();
|
||||
if (!_frames[i])
|
||||
{
|
||||
throw std::runtime_error("Failed to allocate AVFrame");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~VideoBuffer()
|
||||
{
|
||||
for (uint8_t i = 0; i < BUFFER_VIDEO_FRAMES; ++i)
|
||||
{
|
||||
if (_frames[i])
|
||||
{
|
||||
av_frame_free(&_frames[i]);
|
||||
_frames[i] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool latest(AVFrame **frame, uint32_t *id)
|
||||
{
|
||||
_reading.store(_latest.load());
|
||||
int index = _reading.load();
|
||||
if (index < 0)
|
||||
return false;
|
||||
*frame = _frames[index];
|
||||
*id = _ids[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
void consume()
|
||||
{
|
||||
_reading.store(-1);
|
||||
}
|
||||
|
||||
AVFrame *write(uint32_t id)
|
||||
{
|
||||
int index = _writing.load();
|
||||
while (index == _reading.load() || index == _latest.load())
|
||||
{
|
||||
index = (index + 1) % BUFFER_VIDEO_FRAMES;
|
||||
}
|
||||
_writing.store(index);
|
||||
_ids[index] = id;
|
||||
return _frames[index];
|
||||
}
|
||||
|
||||
void commit()
|
||||
{
|
||||
_latest.store(_writing.load());
|
||||
}
|
||||
|
||||
private:
|
||||
void deallocate();
|
||||
|
||||
uint16_t _width;
|
||||
uint16_t _height;
|
||||
std::atomic<int8_t> _latest;
|
||||
std::atomic<int8_t> _reading;
|
||||
std::atomic<int8_t> _writing;
|
||||
@@ -39,4 +83,4 @@ private:
|
||||
uint32_t _ids[BUFFER_VIDEO_FRAMES];
|
||||
};
|
||||
|
||||
#endif /* SRC_VIDEO_BUFFER */
|
||||
#endif /* SRC_STRUCT_VIDEO_BUFFER */
|
||||
|
||||
Reference in New Issue
Block a user