Commit ebf78f4c authored by Linshizhi's avatar Linshizhi

update

parent 1ffe45e7
...@@ -37,7 +37,11 @@ ExternalProject_Add( ...@@ -37,7 +37,11 @@ ExternalProject_Add(
set(SRC ${ROOT}/src) set(SRC ${ROOT}/src)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(SRC_Files 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}) add_library(smp ${SRC_Files})
# Tests # Tests
......
...@@ -2,14 +2,43 @@ ...@@ -2,14 +2,43 @@
namespace IOCtx { 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::InCtx(std::string path, CustomProto proto): 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; AVIOContext *customIO = nullptr;
fmt = Utils::makeInAVFormat(path, customIO); fmt = Utils::makeInAVFormat(path, customIO);
...@@ -17,11 +46,7 @@ InCtx::InCtx(std::string path, CustomProto proto): ...@@ -17,11 +46,7 @@ InCtx::InCtx(std::string path, CustomProto proto):
void InCtx::readFrame(AVPacket *packet) { void InCtx::readFrame(AVPacket *packet) {
if (av_read_frame(fmt.get(), packet) < 0) if (av_read_frame(fmt.get(), packet) < 0)
throw IO_ERROR(); throw END_OF_FILE();
}
bool InCtx::isReady() noexcept {
return fmt.get() != nullptr;
} }
...@@ -29,9 +54,33 @@ bool InCtx::isReady() noexcept { ...@@ -29,9 +54,33 @@ bool InCtx::isReady() noexcept {
// OutCtx // // OutCtx //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
OutCtx::OutCtx(std::string path, CustomProto proto): void OutCtx::writeFrame(AVPacket *packet) {
fmt(nullptr) { 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 <string>
#include <stdexcept> #include <stdexcept>
#include <functional>
#include "utils.h" #include "utils.h"
#include "proto/proto.h"
extern "C" { extern "C" {
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
...@@ -16,10 +18,6 @@ namespace IOCtx { ...@@ -16,10 +18,6 @@ namespace IOCtx {
class InCtx; class InCtx;
class OutCtx; class OutCtx;
// Extra support custom protos
enum CustomProto {
SHARED_MEMORY
};
class END_OF_FILE: public std::runtime_error { class END_OF_FILE: public std::runtime_error {
public: public:
...@@ -31,34 +29,74 @@ public: ...@@ -31,34 +29,74 @@ public:
IO_ERROR(): std::runtime_error("") {} 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 // OutCtx require parameters of InCtx
// to initialize. // to initialize.
friend OutCtx; friend OutCtx;
public: public:
InCtx(std::string path): InCtx(std::string path): InOutCtx(path, false) {
fmt(Utils::makeInAVFormat(path, nullptr)) {} fmt = Utils::makeInAVFormat(path, nullptr);
InCtx(std::string path, CustomProto proto); }
void readFrame(AVPacket*); template<typename T>
bool isReady() noexcept; InCtx(std::string path, IOProto::IOProtocol<T> *proto):
InOutCtx(path, true) {
private: // FIXME: AVIOContext should be generated from proto
Utils::AVFormatContextShared fmt; AVIOContext *ioctx = proto.to_avioctx();
fmt = Utils::makeInAVFormat(path, ioctx);
}
void readFrame(AVPacket*);
}; };
class OutCtx { class OutCtx: public InOutCtx {
public: public:
OutCtx(std::string path): OutCtx(std::string path): InOutCtx(path, false) {
fmt(Utils::makeOutAVFormat(path, nullptr)) {} fmt = Utils::makeOutAVFormat(path, nullptr);
OutCtx(std::string path, CustomProto proto); }
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*); void writeFrame(AVPacket*);
bool isReady() noexcept; IOCTX_ERROR newStream(AVCodecParameters*);
private: void writeHeader();
Utils::AVFormatContextShared fmt; 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 { ...@@ -5,8 +5,12 @@ namespace Utils {
static AVFormatContext* AVFormatInputContextConstructor( static AVFormatContext* AVFormatInputContextConstructor(
std::string path, AVIOContext *customIO) { std::string path, AVIOContext *customIO) {
AVFormatContext *ctx = customIO == nullptr ? nullptr : avformat_alloc_context(); AVFormatContext *ctx = nullptr;
ctx->pb = customIO;
if (customIO != nullptr) {
ctx = avformat_alloc_context();
ctx->pb = customIO;
}
if (avformat_open_input(&ctx, path.c_str(), nullptr, nullptr) < 0) { if (avformat_open_input(&ctx, path.c_str(), nullptr, nullptr) < 0) {
return nullptr; return nullptr;
...@@ -35,10 +39,15 @@ AVFormatContextShared makeInAVFormat(std::string path, AVIOContext *customIO) { ...@@ -35,10 +39,15 @@ AVFormatContextShared makeInAVFormat(std::string path, AVIOContext *customIO) {
static AVFormatContext* AVFormatOutputContextConstructor( static AVFormatContext* AVFormatOutputContextConstructor(
std::string path, AVIOContext *customIO) { std::string path, AVIOContext *customIO) {
AVFormatContext *ctx = customIO == nullptr ? nullptr : avformat_alloc_context(); AVFormatContext *ctx = nullptr;
ctx->pb = customIO;
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; return nullptr;
} }
...@@ -58,4 +67,29 @@ AVFormatContextShared makeOutAVFormat(std::string path, AVIOContext *customIO) { ...@@ -58,4 +67,29 @@ AVFormatContextShared makeOutAVFormat(std::string path, AVIOContext *customIO) {
return ioCtx; 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*); ...@@ -20,6 +20,7 @@ AVFormatContextShared makeOutAVFormat(std::string, AVIOContext*);
* running envrionment */ * running envrionment */
bool isVideoValid(std::string path); bool isVideoValid(std::string path);
} void packetPrepareForOutput(AVPacket *packet, AVStream *inStream, AVStream *outStream);
}
#endif /* UTILS_H */ #endif /* UTILS_H */
...@@ -4,38 +4,73 @@ ...@@ -4,38 +4,73 @@
#include "utils.h" #include "utils.h"
extern "C" { extern "C" {
#include <libavformat/avformat.h> #include <libavformat/avformat.h>
#include <libavutil/avutil.h>
} }
class IOCTX_With_Default_Proto_Fixture: public ::testing::Test { class IOCTX_With_Default_Proto_Fixture: public ::testing::Test {
protected: protected:
void SetUp() override { 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 inFilePath = "./resources/small_bunny_1080p_60fps.mp4";
std::string outFilePath = "./resources/sample_out.mp4"; std::string outFilePath = "./resources/small_bunny_1080p_60fps_out.mp4";
InCtx iCtx { inFilePath }; IOCtx::InCtx iCtx { inFilePath };
OutCtx oCtx; IOCtx::OutCtx oCtx { outFilePath };
}; };
class IOCTX_With_SharedMemory_Proto_Fixture: public ::testing::Test { class IOCTX_With_TransientMem_Proto_Fixture: public ::testing::Test {
protected: protected:
void SetUp() override { void SetUp() override {
} }
InCtx iCtx; std::string inFilePath = "./resources/small_bunny_1080p_60fps.mp4";
OutCtx oCtx; std::string outFilePath = "./resources/small_bunny_1080p_60fps_out.mp4";
}; };
TEST_F(IOCTX_With_Default_Proto_Fixture, Description) { TEST_F(IOCTX_With_Default_Proto_Fixture, Description) {
AVPacket packet; AVPacket packet;
iCtx.readFrame(&packet); AVStream *is = iCtx.getStream([](AVStream *s) {
oCtx.writeFrame(&packet); 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)); 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