#include "FFmpegContext.h" #include namespace { const size_t AVIO_BUFFER_SIZE = 8192; int cfileRead(void* ptr, uint8_t* buf, int buf_size) { auto cfile = reinterpret_cast(ptr); auto numRead = cfread(buf, 1, buf_size, cfile); if (numRead == 0) { // Read failed return -1; } return numRead; } int64_t cfileSeek(void* ptr, int64_t offset, int whence) { auto cfile = reinterpret_cast(ptr); int op = CF_SEEK_SET; switch (whence) { case SEEK_SET: op = CF_SEEK_SET; break; case SEEK_CUR: op = CF_SEEK_CUR; break; case SEEK_END: op = CF_SEEK_END; break; case AVSEEK_SIZE: return cfilelength(cfile); } auto intOff = static_cast(offset); if ((int64_t)intOff != offset) { // Overflow!!! return -1; } auto ret = cfseek(cfile, intOff, op); if (ret != 0) { // Error return -1; } // cfseek returns the offset in the archive file (who thought that would be a good idea?) return cftell(cfile); } int memfile_read(void* opaque, uint8_t* buf, int buf_size) { auto memsound = reinterpret_cast(opaque); auto vec_buf_size = static_cast::size_type>(buf_size); if ((int)vec_buf_size != buf_size) { // Overflow!!! return -1; } vec_buf_size = std::min(vec_buf_size, memsound->snddata.size() - memsound->cursor_pos); if (vec_buf_size > 0) { std::memcpy(buf, memsound->snddata.data() + memsound->cursor_pos, vec_buf_size); memsound->cursor_pos += vec_buf_size; return static_cast(vec_buf_size); } else { return -1; } } int64_t memfile_seek(void* opaque, int64_t offset, int whence) { auto memsound = reinterpret_cast(opaque); if (whence == AVSEEK_SIZE) { return memsound->snddata.size(); } int64_t cursor_pos = memsound->cursor_pos; switch (whence) { case SEEK_SET: cursor_pos = offset; break; case SEEK_CUR: cursor_pos += offset; break; case SEEK_END: cursor_pos = memsound->snddata.size() + offset; break; } memsound->cursor_pos = std::min(static_cast::size_type>(cursor_pos), memsound->snddata.size()); return static_cast(memsound->cursor_pos); } }//namespace namespace libs { namespace ffmpeg { MemFileCursor::MemFileCursor(const uint8_t* data, size_t snd_len) : snddata(data, data + snd_len), cursor_pos(0) { } MemFileCursor::MemFileCursor() : snddata() , cursor_pos(0) {} FFmpegContext::FFmpegContext(CFILE* inFile) : m_ctx(nullptr), m_file(inFile), m_memsound() { Assertion(inFile != nullptr, "Invalid file pointer passed!"); } FFmpegContext::FFmpegContext(const uint8_t* snddata, size_t snd_len) : m_ctx(nullptr), m_file(nullptr), m_memsound(snddata, snd_len) { } FFmpegContext::~FFmpegContext() { if (m_ctx) { if (m_ctx->pb) { if (m_ctx->pb->buffer) { av_free(m_ctx->pb->buffer); } av_free(m_ctx->pb); m_ctx->pb = nullptr; } avformat_close_input(&m_ctx); m_ctx = nullptr; } if (m_file) { cfclose(m_file); m_file = nullptr; } } void FFmpegContext::prepare(void *opaque, int (*read_packet)(void *opaque, uint8_t *buf, int buf_size), int64_t (*seek)(void *opaque, int64_t offset, int whence)) { m_ctx = avformat_alloc_context(); if (!m_ctx) { throw FFmpegException("Failed to allocate context!"); } auto avioBuffer = reinterpret_cast(av_malloc(AVIO_BUFFER_SIZE)); if (!avioBuffer) { throw FFmpegException("Failed to allocate IO buffer!"); } auto ioContext = avio_alloc_context(avioBuffer, AVIO_BUFFER_SIZE, 0, opaque, read_packet, nullptr, seek); if (!ioContext) { throw FFmpegException("Failed to allocate IO context!"); } m_ctx->pb = ioContext; auto probe_ret = av_probe_input_buffer2(m_ctx->pb, &m_ctx->iformat, nullptr, nullptr, 0, 0); if (probe_ret < 0) { char errorStr[1024]; av_strerror(probe_ret, errorStr, 1024); throw FFmpegException(SCP_string("Could not open movie file! Error: ") + errorStr); } m_ctx->flags |= AVFMT_FLAG_CUSTOM_IO; auto ret = avformat_open_input(&m_ctx, nullptr, m_ctx->iformat, nullptr); if (ret < 0) { char errorStr[1024]; av_strerror(ret, errorStr, 1024); throw FFmpegException(SCP_string("Could not open movie file! Error: ") + errorStr); } ret = avformat_find_stream_info(m_ctx, nullptr); if (ret < 0) { char errorStr[1024]; av_strerror(ret, errorStr, 1024); throw FFmpegException(SCP_string("Failed to get stream information! Error: ") + errorStr); } } std::unique_ptr FFmpegContext::createContext(CFILE* mediaFile) { Assertion(mediaFile != nullptr, "File pointer must be valid!"); std::unique_ptr instance(new FFmpegContext(mediaFile)); instance->prepare(instance->m_file, cfileRead, cfileSeek); return instance; } std::unique_ptr FFmpegContext::createContextMem(const uint8_t* snddata, size_t snd_len) { std::unique_ptr instance(new FFmpegContext(snddata, snd_len)); instance->prepare(&(instance->m_memsound), memfile_read, memfile_seek); return instance; } std::unique_ptr FFmpegContext::createContext(const SCP_string& path, int dir_type) { CFILE* file = cfopen(path.c_str(), "rb", dir_type); if (!file) { throw FFmpegException("Failed to open file!"); } return createContext(file); } } }