Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Circular buffer #42

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ enable_testing()

find_package(InnoSetup)

if(NOT DEFINED MSVC OR MSVC_VERSION LESS 1915)
message(FATAL_ERROR "camstudio currently only builds with visual studio 2017 15.8 for now.")
endif()

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
Expand Down
3 changes: 2 additions & 1 deletion bootstrap.cmd
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
rd /s /q vs_build
mkdir vs_build
cd vs_build
cmake ../ -G "Visual Studio 15 2017 Win64" -T host=x64
../cmake/bin/cmake.exe --version
cmake/bin/cmake.exe ../ -G "Visual Studio 16 2019" -A x64 -T host=x64
cd ..
3 changes: 3 additions & 0 deletions src/Encoder/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ set(ENCODER_SOURCE
src/av_error.cpp
src/av_muxer.cpp
src/av_video.cpp
src/circular_frame_buffer.cpp
src/av_log.h
)

Expand All @@ -36,6 +37,7 @@ set(ENCODER_INCLUDE
include/CamEncoder/av_video.h
include/CamEncoder/av_ffmpeg.h
include/CamEncoder/av_encoder.h
include/CamEncoder/circular_frame_buffer.h
)

set(ENCODER_CAM_ENCODER_SOURCE
Expand Down Expand Up @@ -76,6 +78,7 @@ target_compile_definitions(CamEncoder
NOMINMAX
_UNICODE
UNICODE
-D__CRT_SECURE_NO_WARNINGS
)

target_compile_options(CamEncoder
Expand Down
13 changes: 12 additions & 1 deletion src/Encoder/include/CamEncoder/av_muxer.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "av_config.h"
#include "av_audio.h"
#include "av_video.h"
#include "circular_frame_buffer.h"

#include <memory>
#include <string>
Expand Down Expand Up @@ -61,10 +62,16 @@ struct av_metadata
std::string encoding_tool;
};

struct muxer_settings
{
bool use_circular_buffer{false};
int circular_buffer_time{0};
};

class av_muxer
{
public:
av_muxer(std::string filename, const av_muxer_type muxer_type, av_metadata metadata);
av_muxer(muxer_settings settings, std::string filename, const av_muxer_type muxer_type, av_metadata metadata);
~av_muxer();

// open the muxer so its ready to encode stuff.
Expand Down Expand Up @@ -94,6 +101,8 @@ class av_muxer

private:
int write_frame(const AVRational &time_base, AVStream *st, AVPacket *pkt);
void write_circular_frame_buffer();

private:
AVFormatContext *format_context_{ nullptr };
AVOutputFormat *output_format_{ nullptr };
Expand All @@ -104,6 +113,8 @@ class av_muxer
std::string filename_{};
av_metadata metadata_{};
AVRational time_base_{1, 0};
circular_frame_buffer buffer_;
muxer_settings settings_;

// Commented, because we do not handle audio yet.
//int have_audio{0};
Expand Down
42 changes: 42 additions & 0 deletions src/Encoder/include/CamEncoder/circular_frame_buffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright(C) 2019 Steven Hoving
*
* This program is free software : you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

#include "av_ffmpeg.h"
#include <list>
#include <memory>

class circular_frame_buffer
{
public:
explicit circular_frame_buffer(int seconds);
~circular_frame_buffer();
void append_packet(std::unique_ptr<AVPacket> packet);

std::list<std::unique_ptr<AVPacket>>::iterator begin();
std::list<std::unique_ptr<AVPacket>>::iterator end();

private:
void shrink_buffer();
bool buffer_exceeds_limit(int seconds);
void pop_gop();

private:
std::list<std::unique_ptr<AVPacket>> buffer;
int buffer_size_seconds;
};
1 change: 1 addition & 0 deletions src/Encoder/src/av_error.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "CamEncoder/av_error.h"
#include "CamEncoder/av_ffmpeg.h"
#include <stdexcept>

std::string av_error_to_string(int error_number)
{
Expand Down
63 changes: 51 additions & 12 deletions src/Encoder/src/av_muxer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <fmt/format.h>
#include <fmt/time.h>
#include <memory>

void av_log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
{
Expand All @@ -34,9 +35,11 @@ void av_log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt)
pkt->stream_index);
}

av_muxer::av_muxer(std::string filename, const av_muxer_type muxer_type, av_metadata metadata)
av_muxer::av_muxer(muxer_settings settings, std::string filename, const av_muxer_type muxer_type, av_metadata metadata)
: filename_(std::move(filename))
, metadata_(std::move(metadata))
, buffer_(settings.circular_buffer_time)
, settings_(settings)
{
const auto muxer_type_name = av_muxer_type_names.at(static_cast<int>(muxer_type));

Expand Down Expand Up @@ -148,27 +151,39 @@ void av_muxer::open()
void av_muxer::flush()
{
encode_frame(0, nullptr, 0, 0, 0);

if (settings_.use_circular_buffer)
{
write_circular_frame_buffer();
}
}

void av_muxer::encode_frame(timestamp_t timestamp, unsigned char *data, int width, int height, int stride)
{
video_codec_->push_encode_frame(timestamp, data, width, height, stride);

AVPacket pkt = {};
av_init_packet(&pkt);

const auto time_base = video_codec_->get_time_base();

for(bool valid_packet = true; valid_packet;)
for (bool valid_packet = true; valid_packet;)
{
if (!video_codec_->pull_encoded_packet(&pkt, &valid_packet))
auto pkt = std::make_unique<AVPacket>();
av_init_packet(pkt.get());

if (!video_codec_->pull_encoded_packet(pkt.get(), &valid_packet))
throw std::runtime_error("pull encoded packet failed");

if (!valid_packet)
break;

write_frame(time_base, video_track.stream, &pkt);
av_packet_unref(&pkt);
if (settings_.use_circular_buffer)
{
buffer_.append_packet(std::move(pkt));
}
else
{
write_frame(time_base, video_track.stream, pkt.get());
av_packet_unref(pkt.get());
}
}
}

Expand Down Expand Up @@ -288,13 +303,13 @@ int av_muxer::write_audio_frame(av_track *track, AVFrame *frame)
assert(false && !"muxing audio frames is not implemented yet");

AVCodecContext *c = nullptr;
AVPacket pkt = {}; // data and size must be 0;
auto pkt = std::make_unique<AVPacket>(); // data and size must be 0;

int ret = 0;
int got_packet = 0;
int dst_nb_samples = 0;

av_init_packet(&pkt);
av_init_packet(pkt.get());
c = track->codec_context;

if (frame)
Expand Down Expand Up @@ -327,7 +342,7 @@ int av_muxer::write_audio_frame(av_track *track, AVFrame *frame)
// track->samples_count += dst_nb_samples;
}

ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet);
ret = avcodec_encode_audio2(c, pkt.get(), frame, &got_packet);
if (ret < 0)
{
_log("Error encoding audio frame: {}\n", av_error_to_string(ret));
Expand All @@ -336,7 +351,8 @@ int av_muxer::write_audio_frame(av_track *track, AVFrame *frame)

if (got_packet)
{
ret = write_frame(c->time_base, track->stream, &pkt);
ret = write_frame(c->time_base, track->stream, pkt.get());
av_packet_unref(pkt.get());
if (ret < 0)
{
_log("Error while writing audio frame: {}\n", av_error_to_string(ret));
Expand All @@ -363,6 +379,29 @@ int av_muxer::write_frame(const AVRational &time_base, AVStream *stream, AVPacke

/* Write the compressed frame to the media file. */
const auto ret = av_interleaved_write_frame(format_context_, pkt);

assert(ret == 0);
return ret;
}

void av_muxer::write_circular_frame_buffer()
{
const auto time_base = video_codec_->get_time_base();

// Overwrite dts and pts
auto &first_packet = *buffer_.begin();
const auto begin_dts = first_packet->dts;
const auto begin_pts = first_packet->pts;

for (auto &pkt : buffer_)
{
auto &packet = *pkt;
packet.dts = packet.dts - begin_dts;
packet.pts = packet.pts - begin_pts;
}

for (auto &pkt : buffer_)
{
write_frame(time_base, video_track.stream, pkt.get());
}
}
82 changes: 82 additions & 0 deletions src/Encoder/src/circular_frame_buffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright(C) 2019 Steven Hoving
*
* This program is free software : you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

#include "CamEncoder/circular_frame_buffer.h"
#include "fmt/color.h"

circular_frame_buffer::circular_frame_buffer(int seconds)
: buffer_size_seconds(seconds)
{
}

circular_frame_buffer::~circular_frame_buffer()
{
for (auto &packet : buffer)
{
av_packet_unref(packet.get());
}
}

void circular_frame_buffer::append_packet(std::unique_ptr<AVPacket> packet)
{
buffer.emplace_back(std::move(packet));
shrink_buffer();
}

void circular_frame_buffer::pop_gop()
{
do
{
if (buffer.empty())
return;

av_packet_unref(buffer.front().get());
buffer.pop_front();
} while (!(buffer.front()->flags & AV_PKT_FLAG_KEY));
}

void circular_frame_buffer::shrink_buffer()
{
while (buffer_exceeds_limit(buffer_size_seconds))
{
pop_gop();
}
}

std::list<std::unique_ptr<AVPacket>>::iterator circular_frame_buffer::begin()
{
return buffer.begin();
}

std::list<std::unique_ptr<AVPacket>>::iterator circular_frame_buffer::end()
{
return buffer.end();
}

bool circular_frame_buffer::buffer_exceeds_limit(int seconds)
{
if (buffer.begin() == buffer.end())
return false;

const auto begin_pts = buffer.front()->pts;
const auto end_pts = buffer.back()->pts;

const auto diff_pts = end_pts - begin_pts;
const auto buffer_time = diff_pts / 1000;

return buffer_time > seconds;
}
3 changes: 2 additions & 1 deletion src/Encoder/tests/test_cam_encoder/test_muxer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ void test_muxer(const int width, const int height, const int fps, av_muxer_type
}

av_metadata metadata{"test"};
muxer_settings muxer_settings{false, 0};

av_muxer muxer(filename.c_str(), muxer_type, metadata);
av_muxer muxer(muxer_settings, filename.c_str(), muxer_type, metadata);
muxer.add_stream(create_video_codec(config, pixel_format));
muxer.open();

Expand Down
Loading