Commit ebf78f4c authored by Linshizhi's avatar Linshizhi

update

parent 1ffe45e7
......@@ -37,7 +37,11 @@ ExternalProject_Add(
set(SRC ${ROOT}/src)
set(CMAKE_CXX_STANDARD 20)
set(SRC_Files
${SRC}/ioctx.cc)
${SRC}/ioctx.cc
${SRC}/utils.cc
${SRC}/proto/proto.cc
${SRC}/proto/sharedMemProto.cc
${SRC}/proto/transientMemProto.cc)
add_library(smp ${SRC_Files})
# Tests
......
......@@ -2,14 +2,43 @@
namespace IOCtx {
///////////////////////////////////////////////////////////////////////////////
// InOutCtx //
///////////////////////////////////////////////////////////////////////////////
AVStream* InOutCtx::getStream(StreamPrediction predict) {
AVStream *s;
AVFormatContext *fmtCtx = fmt.get();
if (fmtCtx == nullptr) {
return nullptr;
}
int nbStreams = fmtCtx->nb_streams;
for (int i = 0; i < nbStreams; ++i) {
s = fmtCtx->streams[i];
if (predict(s)) {
break;
}
}
return s;
}
bool InOutCtx::isReady() noexcept {
return fmt.get() != nullptr;
}
///////////////////////////////////////////////////////////////////////////////
// InCtx //
///////////////////////////////////////////////////////////////////////////////
InCtx::InCtx(std::string path, CustomProto proto):
fmt(nullptr) {
InOutCtx(path, proto) {
// Build AVIOContex for custom proto
// FIXME: Build AVIOContex for custom proto
AVIOContext *customIO = nullptr;
fmt = Utils::makeInAVFormat(path, customIO);
......@@ -17,11 +46,7 @@ InCtx::InCtx(std::string path, CustomProto proto):
void InCtx::readFrame(AVPacket *packet) {
if (av_read_frame(fmt.get(), packet) < 0)
throw IO_ERROR();
}
bool InCtx::isReady() noexcept {
return fmt.get() != nullptr;
throw END_OF_FILE();
}
......@@ -29,9 +54,33 @@ bool InCtx::isReady() noexcept {
// OutCtx //
///////////////////////////////////////////////////////////////////////////////
OutCtx::OutCtx(std::string path, CustomProto proto):
fmt(nullptr) {
void OutCtx::writeFrame(AVPacket *packet) {
if (av_write_frame(fmt.get(), packet) < 0) {
throw IO_ERROR();
}
}
IOCTX_ERROR OutCtx::newStream(AVCodecParameters* par) {
if (!fmt.get()) {
return ERROR;
}
AVStream *s = avformat_new_stream(fmt.get(), nullptr);
avcodec_parameters_copy(s->codecpar, par);
return OK;
}
void OutCtx::writeHeader() {
if (!isCustomIO && avio_open(
&fmt.get()->pb, path.c_str(), AVIO_FLAG_WRITE) < 0)
throw FAILED_TO_WRITE_HEADER();
if (avformat_write_header(fmt.get(), nullptr) < 0)
throw FAILED_TO_WRITE_HEADER();
}
void OutCtx::writeTrailer() {
av_write_trailer(fmt.get());
}
}
#include <string>
#include <stdexcept>
#include <functional>
#include "utils.h"
#include "proto/proto.h"
extern "C" {
#include <libavformat/avformat.h>
......@@ -16,10 +18,6 @@ namespace IOCtx {
class InCtx;
class OutCtx;
// Extra support custom protos
enum CustomProto {
SHARED_MEMORY
};
class END_OF_FILE: public std::runtime_error {
public:
......@@ -31,34 +29,74 @@ public:
IO_ERROR(): std::runtime_error("") {}
};
class FAILED_TO_WRITE_HEADER: public std::runtime_error {
public:
FAILED_TO_WRITE_HEADER(): std::runtime_error("") {}
};
enum IOCTX_ERROR {
OK,
ERROR,
};
using StreamPrediction = std::function<bool(AVStream*)>;
template<typename T>
class InOutCtx {
public:
InOutCtx(std::string path, bool isCustom):
fmt(nullptr), path(path), isCustomIO(false) {}
AVStream* getStream(StreamPrediction);
bool isReady() noexcept;
protected:
Utils::AVFormatContextShared fmt;
std::string path;
};
class InCtx {
class InCtx: public InOutCtx {
// OutCtx require parameters of InCtx
// to initialize.
friend OutCtx;
public:
InCtx(std::string path):
fmt(Utils::makeInAVFormat(path, nullptr)) {}
InCtx(std::string path, CustomProto proto);
InCtx(std::string path): InOutCtx(path, false) {
fmt = Utils::makeInAVFormat(path, nullptr);
}
void readFrame(AVPacket*);
bool isReady() noexcept;
template<typename T>
InCtx(std::string path, IOProto::IOProtocol<T> *proto):
InOutCtx(path, true) {
private:
Utils::AVFormatContextShared fmt;
// FIXME: AVIOContext should be generated from proto
AVIOContext *ioctx = proto.to_avioctx();
fmt = Utils::makeInAVFormat(path, ioctx);
}
void readFrame(AVPacket*);
};
class OutCtx {
class OutCtx: public InOutCtx {
public:
OutCtx(std::string path):
fmt(Utils::makeOutAVFormat(path, nullptr)) {}
OutCtx(std::string path, CustomProto proto);
OutCtx(std::string path): InOutCtx(path, false) {
fmt = Utils::makeOutAVFormat(path, nullptr);
}
template<typename T>
OutCtx(std::string path, IOProto::IOProtocol<T> *proto):
InOutCtx(path, true) {
// FIXME: AVIOContext should be generated from proto
AVIOContext *ioctx = proto.to_avioctx();
fmt = Utils::makeOutAVFormat(path, ioctx);
}
void writeFrame(AVPacket*);
bool isReady() noexcept;
private:
Utils::AVFormatContextShared fmt;
IOCTX_ERROR newStream(AVCodecParameters*);
void writeHeader();
void writeTrailer();
};
}
......
#include <string>
#include <memory>
#include <functional>
extern "C" {
#include <libavformat/avformat.h>
}
#ifndef PROTO_H
#define PROTO_H
namespace IOProto {
constexpr size_t DEFAULT_BUFFER_SIZE = 1 << 15;
enum RW_FLAG {
read,
write
};
/* IOProtocol used to wrap IO Protocol logics
* and transform into AVIOContext */
template<typename T>
class IOProtocol {
public:
IOProtocol(std::string name, RW_FLAG flag, void *priv):
name(name), flag(flag), io(nullptr), priv(priv) {}
int read_packet(void *priv, uint8_t *buf, int bufSize) {
static_cast<T*>(this)->read_packet_internal(priv, buf, bufSize);
}
int write_packet(void *priv, uint8_t *buf, int bufSize) {
static_cast<T*>(this)->write_packet_internal(priv, buf, bufSize);
}
int64_t seek_packet(void *opaque, int64_t offset, int whence) {
static_cast<T*>(this)->seek_packet_internal(opaque, offset, whence);
}
AVIOContext* to_avioctx() noexcept {
if (io == nullptr) {
buffer = std::make_unique<uint8_t>(DEFAULT_BUFFER_SIZE);
io = avio_alloc_context(buffer.get(), DEFAULT_BUFFER_SIZE, flag, priv,
this->read_packet,
this->write_packet,
this->seek_packet);
}
return io;
}
protected:
RW_FLAG flag;
void *priv;
std::string name;
AVIOContext *io;
std::unique_ptr<uint8_t> buffer;
};
}
#endif /* PROTO_H */
#include "proto.h"
#ifndef PROTOLISTS_H
#define PROTOLISTS_H
namespace IOProto {
}
#endif /* PROTOLISTS_H */
#include "proto.h"
#ifndef SHAREDMEMPROTO_H
#define SHAREDMEMPROTO_H
namespace IOProto {
class SharedMemProto: public IOProtocol<SharedMemProto> {
public:
SharedMemProto(void *priv, RW_FLAG flag):
IOProtocol("", flag, priv) {}
};
}
#endif /* SHAREDMEMPROTO_H */
#include "transientMemProto.h"
#include <memory>
#include <algorithm>
namespace IOProto {
namespace TransientMemProto {
/* External buffer must not be realeased
* by TransientMemProto */
static uint8_t *externalBuffer;
static size_t externalBufferSize;
static uint8_t *bufPtr;
static uint8_t *bufEnd;
static size_t dataSize;
static bool isRleaseExtBuf = false;
bool isEmpty() {
return dataSize == 0;
}
void config_releaseExtBuf(bool onoff) {
isRleaseExtBuf = onoff;
}
/* Note: The passed buf should be full filled */
void attachBuffer(uint8_t *buf, size_t bufSize) {
if (externalBuffer && isRleaseExtBuf)
free(externalBuffer);
externalBuffer = buf;
externalBufferSize = bufSize;
bufPtr = buf;
bufEnd = buf + bufSize;
}
int TransientMemProto::read_packet_internal(void *priv, uint8_t *buf, int bufSize) {
if(flag == write || bufSize < 0 || buf == nullptr) {
AVERROR(EINVAL);
}
size_t readSize = std::min((size_t)bufSize, dataSize);
// Read data
memcpy(buf, bufPtr, readSize);
// Update Buffer status
bufPtr += readSize;
dataSize -= readSize;
return readSize;
}
int TransientMemProto::write_packet_internal(void *priv, uint8_t *buf, int bufSize) {
uint8_t *newBuf = new uint8_t(bufSize);
memcpy(newBuf, buf, bufSize);
attachBuffer(newBuf, bufSize);
return 0;
}
int64_t TransientMemProto::seek_packet_internal(void *opaque, int64_t offset, int whence) {
return 0;
}
}
}
#include "proto.h"
#ifndef TRANSIENTMEMPROTO_H
#define TRANSIENTMEMPROTO_H
namespace IOProto {
namespace TransientMemProto {
void config_releaseExtBuf(bool onoff);
bool isEmpty();
/* Note: The passed buf should be full filled */
void attachBuffer(uint8_t *buf, size_t bufSize);
class TransientMemProto: public IOProtocol<TransientMemProto> {
public:
static constexpr char protoName[] = "TransientMemProto";
TransientMemProto(void *priv, RW_FLAG flag):
IOProtocol(protoName, flag, priv) {}
int read_packet_internal(void *priv, uint8_t *buf, int bufSize);
int write_packet_internal(void *priv, uint8_t *buf, int bufSize);
int64_t seek_packet_internal(void *opaque, int64_t offset, int whence);
};
}
}
#endif /* TRANSIENTMEMPROTO_H */
......@@ -5,8 +5,12 @@ namespace Utils {
static AVFormatContext* AVFormatInputContextConstructor(
std::string path, AVIOContext *customIO) {
AVFormatContext *ctx = customIO == nullptr ? nullptr : avformat_alloc_context();
ctx->pb = customIO;
AVFormatContext *ctx = nullptr;
if (customIO != nullptr) {
ctx = avformat_alloc_context();
ctx->pb = customIO;
}
if (avformat_open_input(&ctx, path.c_str(), nullptr, nullptr) < 0) {
return nullptr;
......@@ -35,10 +39,15 @@ AVFormatContextShared makeInAVFormat(std::string path, AVIOContext *customIO) {
static AVFormatContext* AVFormatOutputContextConstructor(
std::string path, AVIOContext *customIO) {
AVFormatContext *ctx = customIO == nullptr ? nullptr : avformat_alloc_context();
ctx->pb = customIO;
AVFormatContext *ctx = nullptr;
if (avformat_open_input(&ctx, path.c_str(), nullptr, nullptr) < 0) {
if (customIO != nullptr) {
ctx = avformat_alloc_context();
ctx->pb = customIO;
path = path == "" ? path : "";
}
if (avformat_alloc_output_context2(&ctx, nullptr, nullptr, path.c_str()) < 0) {
return nullptr;
}
......@@ -58,4 +67,29 @@ AVFormatContextShared makeOutAVFormat(std::string path, AVIOContext *customIO) {
return ioCtx;
}
bool isVideoValid(std::string path) {
if (path.empty()) {
return false;
}
std::string cmd =
"ffmpeg -v error -i " + path + " -f null - 2>./log.txt";
int ret = system(cmd.c_str());
return ret == 0 ? true : false;
}
void packetPrepareForOutput(AVPacket *packet, AVStream *inStream, AVStream *outStream) {
packet->pts = av_rescale_q_rnd(packet->pts, inStream->time_base, outStream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet->dts = av_rescale_q_rnd(packet->dts, inStream->time_base, outStream->time_base,
static_cast<AVRounding>(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
packet->duration = av_rescale_q(packet->duration, inStream->time_base, outStream->time_base);
packet->stream_index = 0;
packet->pos = -1;
}
}
......@@ -20,6 +20,7 @@ AVFormatContextShared makeOutAVFormat(std::string, AVIOContext*);
* running envrionment */
bool isVideoValid(std::string path);
}
void packetPrepareForOutput(AVPacket *packet, AVStream *inStream, AVStream *outStream);
}
#endif /* UTILS_H */
......@@ -4,38 +4,73 @@
#include "utils.h"
extern "C" {
#include <libavformat/avformat.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
}
class IOCTX_With_Default_Proto_Fixture: public ::testing::Test {
protected:
void SetUp() override {
oCtx = OutCtx(outFilePath, iCtx);
assert(iCtx.isReady());
assert(oCtx.isReady());
AVStream *s = iCtx.getStream([](AVStream *s) {
return s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO;
});
if (oCtx.newStream(s->codecpar) == IOCtx::ERROR)
throw std::runtime_error("Failed to init fixture");
oCtx.writeHeader();
}
void TearDown() override {
std::string cmd = "rm -f " + outFilePath;
system(cmd.c_str());
}
std::string inFilePath = "./resources/sample.mp4";
std::string outFilePath = "./resources/sample_out.mp4";
std::string inFilePath = "./resources/small_bunny_1080p_60fps.mp4";
std::string outFilePath = "./resources/small_bunny_1080p_60fps_out.mp4";
InCtx iCtx { inFilePath };
OutCtx oCtx;
IOCtx::InCtx iCtx { inFilePath };
IOCtx::OutCtx oCtx { outFilePath };
};
class IOCTX_With_SharedMemory_Proto_Fixture: public ::testing::Test {
class IOCTX_With_TransientMem_Proto_Fixture: public ::testing::Test {
protected:
void SetUp() override {
}
InCtx iCtx;
OutCtx oCtx;
std::string inFilePath = "./resources/small_bunny_1080p_60fps.mp4";
std::string outFilePath = "./resources/small_bunny_1080p_60fps_out.mp4";
};
TEST_F(IOCTX_With_Default_Proto_Fixture, Description) {
AVPacket packet;
iCtx.readFrame(&packet);
oCtx.writeFrame(&packet);
AVStream *is = iCtx.getStream([](AVStream *s) {
return s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO;
});
AVStream *os = oCtx.getStream([](AVStream *s) {
return s->codecpar->codec_type == AVMEDIA_TYPE_VIDEO;
});
try {
while (true) {
iCtx.readFrame(&packet);
Utils::packetPrepareForOutput(&packet, is, os);
oCtx.writeFrame(&packet);
av_packet_unref(&packet);
}
} catch(IOCtx::END_OF_FILE e) {}
oCtx.writeTrailer();
ASSERT_TRUE(Utils::isVideoValid(outFilePath));
}
TEST_F(IOCTX_With_TransientMem_Proto_Fixture, Description) {
}
#include <gtest/gtest.h>
class TransientMemProto_Fixture: public ::testing::Test {
protected:
void SetUp() override {
}
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment