diff --git a/.gitignore b/.gitignore index 513eaeb..8a3340f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ build *.kdev4 +.idea +cmake-build-debug/ \ No newline at end of file diff --git a/03-record-sdl/CMakeLists.txt b/03-record-sdl/CMakeLists.txt index c2a2d9f..e02b175 100644 --- a/03-record-sdl/CMakeLists.txt +++ b/03-record-sdl/CMakeLists.txt @@ -2,13 +2,13 @@ cmake_minimum_required(VERSION 2.6) project(ffmpeg-sdl) -set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -std=c++11") +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -std=c++11 -pthread") add_library(sdldemo - src/Area.cpp - src/SdlTexture.cpp - src/SdlException.cpp - src/SdlWindow.cpp + src/Area.cpp + src/SdlTexture.cpp + src/SdlException.cpp + src/SdlWindow.cpp ) # Agrego la carpeta con los headers autogenerados a los includes @@ -16,11 +16,19 @@ message("Agregando '${PROJECT_BINARY_DIR}' como directorio de includes") include_directories(${PROJECT_BINARY_DIR}) add_executable (record-sdl - src/OutputFormat.cpp - src/FormatContext.cpp - src/main.cpp + src/Output.cpp + src/Frame.cpp + src/Packet.cpp + src/FormatOutput.cpp + src/CodecContext.cpp + src/FormatContext.cpp + src/SwsContext.cpp + src/BlockingQueue.cpp + src/Consumer.cpp + src/Thread.cpp + src/main.cpp ) target_link_libraries(record-sdl avformat avcodec avutil swscale sdldemo SDL2 SDL2_image) -install(FILES assets/cat.gif DESTINATION ${CMAKE_BINARY_DIR}) +install(FILES assets/cat.gif DESTINATION ${CMAKE_BINARY_DIR}) \ No newline at end of file diff --git a/03-record-sdl/src/BlockingQueue.cpp b/03-record-sdl/src/BlockingQueue.cpp new file mode 100644 index 0000000..d805001 --- /dev/null +++ b/03-record-sdl/src/BlockingQueue.cpp @@ -0,0 +1,48 @@ +// +// Created by camix on 12/06/19. +// + +#include "BlockingQueue.h" +#include +#include + +BlockingQueue::BlockingQueue() { + this->done = false; +} + +BlockingQueue::~BlockingQueue() = default; + +void BlockingQueue::push(std::vector element) { + std::unique_lock lock(this->m); + this->queue.push(element); + this->cond_variable.notify_all(); +} + +void BlockingQueue::close() { + std::vector s; + push(s); +} + +std::vector BlockingQueue::pop() { + std::unique_lock lock(this->m); + + if (this->done) + throw BlockingQueueDoneException(); + int i = 0; + while (this->queue.empty() && ! this->done) { + this->cond_variable.wait(lock); + i++; + } + + if (this->done) + throw BlockingQueueDoneException(); + + std::vector data = this->queue.front(); + this->queue.pop(); + if (data.empty()) { + this->done = true; + throw BlockingQueueDoneException(); + } + return data; +} + diff --git a/03-record-sdl/src/BlockingQueue.h b/03-record-sdl/src/BlockingQueue.h new file mode 100644 index 0000000..b4c9918 --- /dev/null +++ b/03-record-sdl/src/BlockingQueue.h @@ -0,0 +1,49 @@ +// +// Created by camix on 12/06/19. +// + +#ifndef FFMPEG_DEMO_BLOCKINGQUEUE_H +#define FFMPEG_DEMO_BLOCKINGQUEUE_H + +#define BQ_CLOSED_EXCEPTION "The queue is closed" + +#include +#include +#include + +class BlockingQueue { +private: + std::queue > queue; + std::mutex m; + std::condition_variable cond_variable; + bool done; + +public: + BlockingQueue(); + ~BlockingQueue(); + + //Guarda un elemento en la cola prioritaria. + void push(std::vector); + + //Saca un elemento de la cola prioritaria. + //Si la cola esta vacia espera a que se ingrese un elemento. + //Si se termino de encolar elementos se lanza una excepcion. + std::vector pop(); + + //Cierra la cola. + void close(); +}; + + + +class BlockingQueueDoneException : public std::exception { + virtual const char* what() const throw () { + std::string message = BQ_CLOSED_EXCEPTION; + return message.c_str(); + } + +public: + explicit BlockingQueueDoneException() = default; +}; + +#endif //FFMPEG_DEMO_BLOCKINGQUEUE_H diff --git a/03-record-sdl/src/CodecContext.cpp b/03-record-sdl/src/CodecContext.cpp new file mode 100644 index 0000000..fa3ed76 --- /dev/null +++ b/03-record-sdl/src/CodecContext.cpp @@ -0,0 +1,51 @@ +// +// Created by camix on 10/06/19. +// + +extern "C" { +#include +} +#include "CodecContext.h" + +CodecContext::CodecContext(AVCodec *codec) : +codecContext(avcodec_alloc_context3(codec)) { + if (!codecContext) { + throw CodecContextInitException(); + } + // La resolución debe ser múltiplo de 2 + this->codecContext->width = 352; + this->codecContext->height = 288; + this->codecContext->time_base = {1,25}; + this->codecContext->framerate = {25,1}; + this->codecContext->pix_fmt = AV_PIX_FMT_YUV420P; + this->codecContext->gop_size = 10; + this->codecContext->max_b_frames = 2; + if (codec->id == AV_CODEC_ID_H264) { + this->codecContext->profile = FF_PROFILE_H264_BASELINE; + av_opt_set(this->codecContext->priv_data, "preset", "slow", 0); + } + avcodec_open2(this->codecContext, codec, NULL); +} + +CodecContext::~CodecContext() { + avcodec_close(this->codecContext); + avcodec_free_context(&this->codecContext); +} + + +int CodecContext::getPixFmt() { + return this->codecContext->pix_fmt; +} + + +int CodecContext::getWidth() { + return this->codecContext->width; +} + +int CodecContext::getHeight() { + return this->codecContext->height; +} + +AVCodecContext* CodecContext::get() { + return this->codecContext; +} \ No newline at end of file diff --git a/03-record-sdl/src/CodecContext.h b/03-record-sdl/src/CodecContext.h new file mode 100644 index 0000000..58e10a9 --- /dev/null +++ b/03-record-sdl/src/CodecContext.h @@ -0,0 +1,51 @@ +// +// Created by camix on 10/06/19. +// + +#ifndef FFMPEG_DEMO_CODECCONTEXT_H +#define FFMPEG_DEMO_CODECCONTEXT_H + +#define INIT_CODEC_CONTEXT_EXC "There was an error while allocating memory for a CodecContext\n" +extern "C" { +#include +} + +#include + +class CodecContextException : public std::exception { +protected: + std::string message; +public: + explicit CodecContextException() = default; + virtual const char *what() const throw() { + return this->message.c_str(); + } +}; + +class CodecContextInitException : public CodecContextException { +public: + explicit CodecContextInitException() { + message = INIT_CODEC_CONTEXT_EXC; + } +}; + +class CodecContext { +private: + AVCodecContext* codecContext; + +public: + explicit CodecContext(AVCodec *codec); + + ~CodecContext(); + + AVCodecContext *get(); + + int getHeight(); + + int getWidth(); + + int getPixFmt(); +}; + + +#endif //FFMPEG_DEMO_CODECCONTEXT_H diff --git a/03-record-sdl/src/Constants.h b/03-record-sdl/src/Constants.h new file mode 100644 index 0000000..cad0439 --- /dev/null +++ b/03-record-sdl/src/Constants.h @@ -0,0 +1,12 @@ +// +// Created by camix on 12/06/19. +// + +#ifndef FFMPEG_DEMO_CONSTANTS_H +#define FFMPEG_DEMO_CONSTANTS_H + +#define BUFFER_WIDTH 352 +#define BUFFER_HEIGHT 288 + + +#endif //FFMPEG_DEMO_CONSTANTS_H diff --git a/03-record-sdl/src/Consumer.cpp b/03-record-sdl/src/Consumer.cpp new file mode 100644 index 0000000..ae8da73 --- /dev/null +++ b/03-record-sdl/src/Consumer.cpp @@ -0,0 +1,33 @@ +// +// Created by camix on 12/06/19. +// +extern "C" { +#include +} +#include "Consumer.h" +#include "Constants.h" + +Consumer::Consumer(BlockingQueue& producedFrames, std::string& filename) : + producedFrames(producedFrames), + videoOutput(context, filename), + ctx(sws_getContext(BUFFER_WIDTH, BUFFER_HEIGHT, + AV_PIX_FMT_RGB24, BUFFER_WIDTH, BUFFER_HEIGHT, + AV_PIX_FMT_YUV420P, 0, 0, 0, 0)) { +} + +void Consumer::run() { + try { + while (true) { + std::vector frame = this->producedFrames.pop(); + videoOutput.writeFrame(frame.data(), ctx); + } + } + catch (BlockingQueueDoneException& e) { + return; + } +} + +Consumer::~Consumer() { + videoOutput.close(); + sws_freeContext(ctx); +} \ No newline at end of file diff --git a/03-record-sdl/src/Consumer.h b/03-record-sdl/src/Consumer.h new file mode 100644 index 0000000..35adde6 --- /dev/null +++ b/03-record-sdl/src/Consumer.h @@ -0,0 +1,34 @@ +// +// Created by camix on 12/06/19. +// + +#ifndef FFMPEG_DEMO_CONSUMER_H +#define FFMPEG_DEMO_CONSUMER_H + + +#include "Thread.h" +#include "BlockingQueue.h" +#include "Output.h" +#include "FormatContext.h" + +class Consumer : public Thread { +private: + BlockingQueue& producedFrames; + FormatContext context; + Output videoOutput; + // You need it to perform scaling/conversion operations using. + SwsContext* ctx; + +public: + /// first you need to Initialize libavformat and register all the muxers, demuxers and + /// protocols with av_register_all(); + Consumer(BlockingQueue& producedImages, std::string& filename); + + ~Consumer(); + + virtual void run() override; + +}; + + +#endif //FFMPEG_DEMO_CONSUMER_H diff --git a/03-record-sdl/src/FormatOutput.cpp b/03-record-sdl/src/FormatOutput.cpp new file mode 100644 index 0000000..f4134f6 --- /dev/null +++ b/03-record-sdl/src/FormatOutput.cpp @@ -0,0 +1,41 @@ +// +// Created by camix on 10/06/19. +// +extern "C" { +#include +} + +#include "FormatOutput.h" +#include "CodecContext.h" + +FormatOutput::FormatOutput(const std::string& filename) { + // Intenta deducir formato según extensión + this->avOutputFormat = av_guess_format(NULL, filename.c_str(), NULL); + if (!this->avOutputFormat) { + // Intenta usar el formato standard + this->avOutputFormat = av_guess_format("mpeg", NULL, NULL); + } + if (!this->avOutputFormat) { + throw FormatOutputInitException(); + } + // h.264 es bastante popular, pero hay mejores + this->avOutputFormat->video_codec = AV_CODEC_ID_H264; + + + + this->codec = avcodec_find_encoder(this->avOutputFormat->video_codec); + if (!codec) { + throw FormatOutputInitException("No se pudo instanciar codec"); + } + //codecContext.init(codec); +} + + +AVCodec *FormatOutput::getCodec() { + return this->codec; +} + + +FormatOutput::~FormatOutput() { + +} diff --git a/03-record-sdl/src/FormatOutput.h b/03-record-sdl/src/FormatOutput.h new file mode 100644 index 0000000..1b0973e --- /dev/null +++ b/03-record-sdl/src/FormatOutput.h @@ -0,0 +1,48 @@ +// +// Created by camix on 10/06/19. +// + +#ifndef FFMPEG_DEMO_FORMATOUTPUT_H +#define FFMPEG_DEMO_FORMATOUTPUT_H + +#define INIT_FORMAT_OUTPUT_EXC "No output format was found" + +#include + +extern "C" { +#include +} + +class FormatOutputException : public std::exception { +protected: + std::string message; +public: + FormatOutputException() = default; + virtual const char *what() const throw() { + return this->message.c_str(); + } +}; + +class FormatOutputInitException : public FormatOutputException { +public: + FormatOutputInitException() { + message = INIT_FORMAT_OUTPUT_EXC; + } + explicit FormatOutputInitException(std::string msg) { + message = msg; + } +}; + +class FormatOutput { +private: + AVOutputFormat* avOutputFormat; + AVCodec *codec; + +public: + explicit FormatOutput(const std::string& filename); + ~FormatOutput(); + AVCodec *getCodec(); +}; + + +#endif //FFMPEG_DEMO_FORMATOUTPUT_H diff --git a/03-record-sdl/src/Frame.cpp b/03-record-sdl/src/Frame.cpp new file mode 100644 index 0000000..2ce8bd6 --- /dev/null +++ b/03-record-sdl/src/Frame.cpp @@ -0,0 +1,35 @@ +// +// Created by camix on 04/06/19. +// + +#include "Frame.h" +extern "C" { +#include +} + +Frame::Frame(int format, int width, int height) : frame(av_frame_alloc()){ + if (!frame){ + throw FrameInitException(); + } + + this->frame->format = format; + this->frame->width = width; + this->frame->height = height; + + av_frame_get_buffer(this->frame, 0); + this->currentPts = 0; +} + + +Frame::~Frame() { + av_frame_free(&frame); +} + +void Frame::write(const char *data, SwsContext *ctx) { + const u_int8_t* tmp = (const u_int8_t*) data; + // El ancho del video x3 por la cantidad de bytes + int width = 352 * 3; + sws_scale(ctx, &tmp, &width, 0, frame->height, frame->data, frame->linesize); + frame->pts = currentPts; + currentPts++; +} diff --git a/03-record-sdl/src/Frame.h b/03-record-sdl/src/Frame.h new file mode 100644 index 0000000..29cfd1c --- /dev/null +++ b/03-record-sdl/src/Frame.h @@ -0,0 +1,51 @@ +// +// Created by camix on 04/06/19. +// + +#ifndef PORTAL_FRAME_H +#define PORTAL_FRAME_H + +#include +extern "C" { +#include +#include +} + +#define INIT_FRAME_EXC "There was an error while allocating memory for a Frame\n" + +class FrameException : public std::exception { +protected: + std::string message; +public: + explicit FrameException() = default; + virtual const char *what() const throw() { + return this->message.c_str(); + } +}; + +class FrameInitException : public FrameException { +public: + explicit FrameInitException() { + message = INIT_FRAME_EXC; + } +}; + +class SwsContext; + +class Frame { +private: + AVFrame* frame; + int currentPts; + +public: + Frame(int format, int width, int height); + ~Frame(); + + void write(const char *data, SwsContext *ctx); + + AVFrame* get() {return this->frame;} +}; + + + +#endif //PORTAL_FRAME_H diff --git a/03-record-sdl/src/Output.cpp b/03-record-sdl/src/Output.cpp new file mode 100644 index 0000000..c733ef0 --- /dev/null +++ b/03-record-sdl/src/Output.cpp @@ -0,0 +1,63 @@ +#include "Output.h" +#include "FormatContext.h" +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +static void encode(CodecContext* codecContext, Frame* frame, Packet* pkt, + FILE *outfile) { + int ret = 0; + AVCodecContext* enc_ctx = codecContext->get(); + if (!frame) { + ret = avcodec_send_frame(enc_ctx, NULL); + } else { + ret = avcodec_send_frame(enc_ctx, frame->get()); + } + if (ret < 0) { + throw std::runtime_error("Error al enviar frame"); + } + while (ret >= 0) { + ret = avcodec_receive_packet(enc_ctx, pkt->get()); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return; + else if (ret < 0) { + throw std::runtime_error("Error al codificar"); + } + pkt->write(outfile); + } +} + +Output::Output(FormatContext& context, const std::string& filename) : + context(context) , + format(filename), + codecContext(format.getCodec()), + frame(codecContext.getPixFmt(), codecContext.getWidth(), codecContext.getHeight()) { + this->outputFile = fopen(filename.c_str(), "wb"); +} + +void Output::close() { + encode(&codecContext, nullptr, &pkt, this->outputFile); + // add sequence end code to have a real MPEG file + uint8_t endCode[] = { 0, 0, 1, 0xb7 }; + fwrite(endCode, 1, sizeof(endCode), this->outputFile); +} + + + +void Output::writeFrame(const char* data, SwsContext* ctx) { + frame.write(data, ctx); + encode(&codecContext, &frame, &pkt, outputFile); +} + + +Output::~Output() { + fclose(this->outputFile); +} diff --git a/03-record-sdl/src/OutputFormat.h b/03-record-sdl/src/Output.h similarity index 61% rename from 03-record-sdl/src/OutputFormat.h rename to 03-record-sdl/src/Output.h index 638f91d..4f2fc54 100644 --- a/03-record-sdl/src/OutputFormat.h +++ b/03-record-sdl/src/Output.h @@ -1,6 +1,10 @@ #ifndef OUTPUTFORMAT_H #define OUTPUTFORMAT_H #include +#include "Frame.h" +#include "Packet.h" +#include "FormatOutput.h" +#include "CodecContext.h" class AVCodec; class AVFrame; @@ -14,29 +18,24 @@ class SwsContext; * Clase que encapsula lógica la salida de video * Se recomienda modularizar aun más esta clase, reforzando RAII */ -class OutputFormat { +class Output { +private: + FormatContext& context; + FormatOutput format; + CodecContext codecContext; + FILE* outputFile; + Frame frame; + Packet pkt; + public: // Ctor - OutputFormat(FormatContext& context, const std::string& filename); + Output(FormatContext& context, const std::string& filename); // Dtor - ~OutputFormat(); + ~Output(); // Escribe un frame a disco. Utiliza `swsContext` para convertir // de RGB24 a YUV420p void writeFrame(const char* data, SwsContext* swsContext); // Cierra el stream de video void close(); -private: - // Inicializa frame - void initFrame(); - // Inicializa contexto de codec - void codecContextInit(AVCodec* codec); - FormatContext& context; - AVOutputFormat* avOutputFormat; - AVStream* video_avstream; - AVCodecContext* codecContext; - int currentPts; - FILE* outputFile; - AVFrame* frame; - AVPacket* pkt; }; #endif diff --git a/03-record-sdl/src/OutputFormat.cpp b/03-record-sdl/src/OutputFormat.cpp deleted file mode 100644 index aa739c3..0000000 --- a/03-record-sdl/src/OutputFormat.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include "OutputFormat.h" -#include "FormatContext.h" -#include -#include -#include -extern "C" { -#include -#include -#include -#include -} - -static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt, - FILE *outfile) { - int ret = avcodec_send_frame(enc_ctx, frame); - if (ret < 0) { - throw std::runtime_error("Error al enviar frame"); - } - while (ret >= 0) { - ret = avcodec_receive_packet(enc_ctx, pkt); - if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) - return; - else if (ret < 0) { - throw std::runtime_error("Error al codificar"); - } - fwrite(pkt->data, 1, pkt->size, outfile); - av_packet_unref(pkt); - } -} - -OutputFormat::OutputFormat(FormatContext& context, - const std::string& filename) : context(context) { - this->frame = av_frame_alloc(); - if (!frame) { - throw std::runtime_error("No se pudo reservar memoria para frame"); - } - this->pkt = av_packet_alloc(); - // Intenta deducir formato según extensión - this->avOutputFormat = av_guess_format(NULL, filename.c_str(), NULL); - if (!this->avOutputFormat) { - // Intenta usar el formato standard - this->avOutputFormat = av_guess_format("mpeg", NULL, NULL); - } - if (!this->avOutputFormat) { - throw std::runtime_error("No se encontró formato de salida"); - } - // h.264 es bastante popular, pero hay mejores - this->avOutputFormat->video_codec = AV_CODEC_ID_H264; - AVCodec *codec = avcodec_find_encoder(this->avOutputFormat->video_codec); - if (!codec) { - throw std::runtime_error("No se pudo instanciar codec"); - } - codecContextInit(codec); - this->outputFile = fopen(filename.c_str(), "wb"); - initFrame(); -} - -void OutputFormat::close() { - encode(this->codecContext, NULL, this->pkt, this->outputFile); - /* add sequence end code to have a real MPEG file */ - uint8_t endcode[] = { 0, 0, 1, 0xb7 }; - fwrite(endcode, 1, sizeof(endcode), this->outputFile); -} - -void OutputFormat::initFrame() { - this->frame->format = this->codecContext->pix_fmt; - this->frame->width = this->codecContext->width; - this->frame->height = this->codecContext->height; - - av_frame_get_buffer(this->frame, 0); - this->currentPts = 0; -} - -void OutputFormat::writeFrame(const char* data, SwsContext* ctx ) { - const u_int8_t* tmp = (const u_int8_t*) data; - // El ancho del video x3 por la cantidad de bytes - int width = 352 * 3; - sws_scale(ctx, &tmp, &width, 0, frame->height, frame->data, frame->linesize); - //drawFrame(frame, data); - frame->pts = currentPts; - currentPts++; - /* encode the image */ - encode(this->codecContext, frame, pkt, this->outputFile); -} - -void OutputFormat::codecContextInit(AVCodec* codec){ - this->codecContext = avcodec_alloc_context3(codec); - // La resolución debe ser múltiplo de 2 - this->codecContext->width = 352; - this->codecContext->height = 288; - this->codecContext->time_base = {1,25}; - this->codecContext->framerate = {25,1}; - this->codecContext->pix_fmt = AV_PIX_FMT_YUV420P; - this->codecContext->gop_size = 10; - this->codecContext->max_b_frames = 2; - if (codec->id == AV_CODEC_ID_H264) { - this->codecContext->profile = FF_PROFILE_H264_BASELINE; - av_opt_set(this->codecContext->priv_data, "preset", "slow", 0); - } - avcodec_open2(this->codecContext, codec, NULL); -} - -OutputFormat::~OutputFormat() { - avcodec_close(this->codecContext); - avcodec_free_context(&this->codecContext); - av_packet_free(&pkt); - av_frame_free(&frame); - fclose(this->outputFile); -} diff --git a/03-record-sdl/src/Packet.cpp b/03-record-sdl/src/Packet.cpp new file mode 100644 index 0000000..0d6c50d --- /dev/null +++ b/03-record-sdl/src/Packet.cpp @@ -0,0 +1,28 @@ +// +// Created by camix on 10/06/19. +// + +#include +#include "Packet.h" + +Packet::Packet() : + pkt(av_packet_alloc()) { + if (!pkt) { + throw PacketInitException(); + } +} + +AVPacket* Packet::get() { + return pkt; +} + +void Packet::write(FILE *outfile) { + //esto es lento porque es escritura en disco + fwrite(pkt->data, 1, pkt->size, outfile); + av_packet_unref(pkt); +} + +Packet::~Packet() { + std::cerr << "entro a destruir el packet" << std::endl; + av_packet_free(&pkt); +} diff --git a/03-record-sdl/src/Packet.h b/03-record-sdl/src/Packet.h new file mode 100644 index 0000000..a671543 --- /dev/null +++ b/03-record-sdl/src/Packet.h @@ -0,0 +1,48 @@ +// +// Created by camix on 10/06/19. +// + +#ifndef FFMPEG_DEMO_PACKET_H +#define FFMPEG_DEMO_PACKET_H + + +#define INIT_PKT_EXC "There was an error while allocating memory for a Packet\n" + +#include + +extern "C" { +#include +} + +class PacketException : public std::exception { +protected: + std::string message; +public: + PacketException() = default; + virtual const char *what() const throw() { + return this->message.c_str(); + } +}; + +class PacketInitException : public PacketException { +public: + PacketInitException() { + message = INIT_PKT_EXC; + } +}; + +class Packet { +private: + AVPacket* pkt; + +public: + Packet(); + ~Packet(); + + AVPacket *get(); + + void write(FILE *outfile); +}; + + +#endif //FFMPEG_DEMO_PACKET_H diff --git a/03-record-sdl/src/SwsContext.cpp b/03-record-sdl/src/SwsContext.cpp new file mode 100644 index 0000000..d9e18af --- /dev/null +++ b/03-record-sdl/src/SwsContext.cpp @@ -0,0 +1,35 @@ +// +// Created by camix on 10/06/19. +// + +#include +#include +#include +#include "SwsContext.h" +#include "Constants.h" + + + +SwsContext::SwsContext(BlockingQueue& producedFrames) : + // Este buffer tiene el tamaño de la sección de SDL que quiero leer, multiplico + // x3 por la cantidad de bytes (8R,8G,8B) + // A sws parece que no le gusta este tamaño + dataBuffer(BUFFER_WIDTH*BUFFER_HEIGHT*3), + producedFrames(producedFrames) + {} + +SwsContext::~SwsContext() = default; + + + +void SwsContext::write(SdlWindow& window) { + // Obtengo los bytes de la textura en el buffer + int res = SDL_RenderReadPixels(window.getRenderer(), NULL, SDL_PIXELFORMAT_RGB24, dataBuffer.data(), BUFFER_WIDTH * 3); + if (res) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "RendererReadPixels error", SDL_GetError(), NULL); + throw SwsContextRendererReadPixelsException(); + } + //operacion lenta que pasó a otro thread: + //videoOutput.writeFrame(dataBuffer.data(), ctx); + producedFrames.push(dataBuffer); +} diff --git a/03-record-sdl/src/SwsContext.h b/03-record-sdl/src/SwsContext.h new file mode 100644 index 0000000..24d6e6f --- /dev/null +++ b/03-record-sdl/src/SwsContext.h @@ -0,0 +1,45 @@ +// +// Created by camix on 10/06/19. +// + +#ifndef FFMPEG_DEMO_SWSCONTEXT_H +#define FFMPEG_DEMO_SWSCONTEXT_H + + +#define RRP_SWS_CONTEXT_EXC "There was an error while writing the frame\n" + +#include +#include +#include "FormatContext.h" +#include "Output.h" +#include "SdlWindow.h" +#include "BlockingQueue.h" +#include "Consumer.h" + +extern "C" { +#include +#include +} + + +class SwsContext { +private: + std::vector dataBuffer; + BlockingQueue& producedFrames; + +public: + SwsContext(BlockingQueue& producedFrames); + ~SwsContext(); + void write(SdlWindow& window); +}; + +class SwsContextRendererReadPixelsException : public std::exception { +public: + SwsContextRendererReadPixelsException() = default; + virtual const char *what() const throw() { + return RRP_SWS_CONTEXT_EXC; + } +}; + + +#endif //FFMPEG_DEMO_SWSCONTEXT_H diff --git a/03-record-sdl/src/Thread.cpp b/03-record-sdl/src/Thread.cpp new file mode 100644 index 0000000..c00221c --- /dev/null +++ b/03-record-sdl/src/Thread.cpp @@ -0,0 +1,22 @@ +// +// Created by camix on 12/06/19. +// + +#include "Thread.h" + + +Thread::Thread(Thread&& other) { + this->thread = std::move(other.thread); +} +Thread::Thread() = default; +Thread::~Thread() = default; + +void Thread::start() { + this->thread = std::thread(&Thread::run, this); +} + + +void Thread::join() { + this->thread.join(); +} + diff --git a/03-record-sdl/src/Thread.h b/03-record-sdl/src/Thread.h new file mode 100644 index 0000000..431d34e --- /dev/null +++ b/03-record-sdl/src/Thread.h @@ -0,0 +1,27 @@ +// +// Created by camix on 12/06/19. +// + +#ifndef FFMPEG_DEMO_THREAD_H +#define FFMPEG_DEMO_THREAD_H + + +#include + +class Thread { +private: + std::thread thread; + +public: + Thread(); + void start(); + void join(); + virtual void run() = 0; + Thread(const Thread&) = delete; + Thread& operator=(const Thread&) = delete; + virtual ~Thread(); + Thread(Thread&& other); +}; + + +#endif //FFMPEG_DEMO_THREAD_H diff --git a/03-record-sdl/src/main.cpp b/03-record-sdl/src/main.cpp index a03de47..20b815b 100644 --- a/03-record-sdl/src/main.cpp +++ b/03-record-sdl/src/main.cpp @@ -9,7 +9,9 @@ extern "C" { #include } #include "FormatContext.h" -#include "OutputFormat.h" +#include "Output.h" +#include "SwsContext.h" +#include "Consumer.h" const int BUFFER_WIDTH = 352, BUFFER_HEIGHT = 288; @@ -21,9 +23,13 @@ int main(int argc, char** argv){ return -1; } try { + BlockingQueue queue; av_register_all(); - FormatContext context; - OutputFormat videoOutput(context, argv[1]); + SwsContext ctx(queue); + std::string filename = argv[1]; + Consumer consumer(queue, filename); + + SdlWindow window(800, 600); window.fill(); // Usar factory @@ -35,14 +41,8 @@ int main(int argc, char** argv){ // Textura sobre la que voy a renderizar lo que quiero grabar. SDL_Texture* videoTexture = SDL_CreateTexture(window.getRenderer(), SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_TARGET, BUFFER_WIDTH, BUFFER_HEIGHT); - // Contexto para escalar archivos. - SwsContext * ctx = sws_getContext(BUFFER_WIDTH, BUFFER_HEIGHT, - AV_PIX_FMT_RGB24, BUFFER_WIDTH, BUFFER_HEIGHT, - AV_PIX_FMT_YUV420P, 0, 0, 0, 0); - // Este buffer tiene el tamaño de la sección de SDL que quiero leer, multiplico - // x3 por la cantidad de bytes (8R,8G,8B) - // A sws parece que no le gusta este tamaño - std::vector dataBuffer(BUFFER_WIDTH*BUFFER_HEIGHT*3); + + consumer.start(); while (running) { // Muevo textura con flechas direccionales handleSDLEvent(x, y, running); @@ -57,17 +57,13 @@ int main(int argc, char** argv){ catTexture.render(srcArea, destArea); // Efectivamente renderiza window.render(); - // Obtengo los bytes de la textura en el buffer - int res = SDL_RenderReadPixels(window.getRenderer(), NULL, SDL_PIXELFORMAT_RGB24, dataBuffer.data(), BUFFER_WIDTH * 3); - if (res) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "RendererReadPixels error", SDL_GetError(), NULL); - break; - } - videoOutput.writeFrame(dataBuffer.data(), ctx); + + + ctx.write(window); } - videoOutput.close(); - // Libero escalador - sws_freeContext(ctx); + + queue.close(); + consumer.join(); } catch (std::exception& e) { std::cout << e.what() << std::endl; return 1; diff --git a/README.md b/README.md index 64acccf..fb82fe9 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,10 @@ mkdir build cd build cmake .. make -j +cd 03-record-sdl +./record-sdl out.mkv +mplayer out.mkv // Se recomienda `mplayer` + // para poder visualizar el mismo. ~~~ Con *N cores* como el número de procesos en paralelo para compilar.