From 10dc1f8cc4267e6b7918ffe351e3290335615248 Mon Sep 17 00:00:00 2001 From: Sandro Kalatozishvili Date: Tue, 23 Jan 2024 12:28:54 +0400 Subject: [PATCH] Initial commit --- .github/workflows/build.yml | 50 +++ .github/workflows/cmake.yml | 42 ++ .github/workflows/make.yml | 34 ++ .github/workflows/smake.yml | 44 ++ .vscode/settings.json | 34 ++ CMakeLists.txt | 37 ++ LICENSE | 21 + Makefile | 45 +++ README.md | 57 +++ build.sh | 147 +++++++ clean.sh | 17 + example/CMakeLists.txt | 39 ++ example/Makefile | 33 ++ example/smake.json | 29 ++ example/xmedia.c | 732 ++++++++++++++++++++++++++++++++++ misc/cmake.tmp | 37 ++ misc/make.tmp | 45 +++ misc/smake.tmp | 24 ++ smake.json | 24 ++ src/codec.c | 724 +++++++++++++++++++++++++++++++++ src/codec.h | 114 ++++++ src/decoder.c | 277 +++++++++++++ src/decoder.h | 62 +++ src/encoder.c | 774 ++++++++++++++++++++++++++++++++++++ src/encoder.h | 105 +++++ src/frame.c | 517 ++++++++++++++++++++++++ src/frame.h | 74 ++++ src/meta.c | 165 ++++++++ src/meta.h | 47 +++ src/mpegts.c | 510 ++++++++++++++++++++++++ src/mpegts.h | 217 ++++++++++ src/nalu.c | 145 +++++++ src/nalu.h | 40 ++ src/status.c | 137 +++++++ src/status.h | 47 +++ src/stdinc.h | 49 +++ src/stream.c | 172 ++++++++ src/stream.h | 64 +++ src/version.c | 50 +++ src/version.h | 31 ++ 40 files changed, 5812 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/cmake.yml create mode 100644 .github/workflows/make.yml create mode 100644 .github/workflows/smake.yml create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100755 build.sh create mode 100755 clean.sh create mode 100644 example/CMakeLists.txt create mode 100644 example/Makefile create mode 100644 example/smake.json create mode 100644 example/xmedia.c create mode 100644 misc/cmake.tmp create mode 100644 misc/make.tmp create mode 100644 misc/smake.tmp create mode 100644 smake.json create mode 100644 src/codec.c create mode 100644 src/codec.h create mode 100644 src/decoder.c create mode 100644 src/decoder.h create mode 100644 src/encoder.c create mode 100644 src/encoder.h create mode 100644 src/frame.c create mode 100644 src/frame.h create mode 100644 src/meta.c create mode 100644 src/meta.h create mode 100644 src/mpegts.c create mode 100644 src/mpegts.h create mode 100644 src/nalu.c create mode 100644 src/nalu.h create mode 100644 src/status.c create mode 100644 src/status.h create mode 100644 src/stdinc.h create mode 100644 src/stream.c create mode 100644 src/stream.h create mode 100644 src/version.c create mode 100644 src/version.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d3689c6 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,50 @@ +name: Full Build + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc g++ cmake make + sudo apt-get install -y libavcodec-dev libavformat-dev + sudo apt-get install -y libavutil-dev libswscale-dev libavdevice-dev + + - name: Checkout SMake + uses: actions/checkout@v3 + with: + repository: 'kala13x/smake' + submodules: recursive + path: 'smake' + + - name: Checkout libxutils + uses: actions/checkout@v3 + with: + repository: 'kala13x/libxutils' + path: 'libxutils' + + - name: Install SMake + run: cd smake && ./build.sh --install && smake -V + + - name: Install libxutils + run: cd libxutils && ./build.sh --install && xutils + + - name: Build with SMake + run: ./build.sh --tool=smake --example + + - name: Build with CMAKE + run: ./build.sh --tool=cmake --example + + - name: Build with Makefile + run: ./build.sh --tool=make --example diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 0000000..b237e96 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,42 @@ +name: CMake + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Checkout libxutils + uses: actions/checkout@v3 + with: + repository: 'kala13x/libxutils' + path: 'libxutils' + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc g++ cmake make + sudo apt-get install -y libavcodec-dev libavformat-dev + sudo apt-get install -y libavutil-dev libswscale-dev libavdevice-dev + + - name: Check versions + run: | + gcc --version + cmake --version + + - name: Build and install libxutils + run: cd libxutils && ./build.sh --tool=cmake --install + + - name: Make build + run: | + mkdir build + cd build + cmake .. + make diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml new file mode 100644 index 0000000..143f850 --- /dev/null +++ b/.github/workflows/make.yml @@ -0,0 +1,34 @@ +name: C/C++ CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc g++ make + sudo apt-get install -y libavcodec-dev libavformat-dev + sudo apt-get install -y libavutil-dev libswscale-dev libavdevice-dev + + - name: Checkout libxutils + uses: actions/checkout@v3 + with: + repository: 'kala13x/libxutils' + path: 'libxutils' + + - name: Build and install libxutils + run: cd libxutils && ./build.sh --install + + - name: Build Project + run: make diff --git a/.github/workflows/smake.yml b/.github/workflows/smake.yml new file mode 100644 index 0000000..51fdf80 --- /dev/null +++ b/.github/workflows/smake.yml @@ -0,0 +1,44 @@ +name: SMake + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Checkout SMake + uses: actions/checkout@v3 + with: + repository: 'kala13x/smake' + submodules: recursive + path: 'smake' + + - name: Checkout libxutils + uses: actions/checkout@v3 + with: + repository: 'kala13x/libxutils' + path: 'libxutils' + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y gcc g++ cmake make + sudo apt-get install -y libavcodec-dev libavformat-dev + sudo apt-get install -y libavutil-dev libswscale-dev libavdevice-dev + + - name: Build and install SMake + run: cd smake && ./build.sh --install + + - name: Build and install libxutils + run: cd libxutils && ./build.sh --install + + - name: Build with SMake + run: smake && make diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..38eddd8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,34 @@ +{ + "files.associations": { + "xstd.h": "c", + "encoder.h": "c", + "stdinc.h": "c", + "cmath": "c", + "array": "c", + "string_view": "c", + "initializer_list": "c", + "numbers": "c", + "xstr.h": "c", + "random": "c", + "deque": "c", + "string": "c", + "unordered_map": "c", + "vector": "c", + "system_error": "c", + "decoder.h": "c", + "array.h": "c", + "meta.h": "c", + "compare": "c", + "functional": "c", + "tuple": "c", + "type_traits": "c", + "utility": "c", + "nalu.h": "c", + "frame.h": "c", + "version.h": "c", + "stream.h": "c", + "status.h": "c", + "xtime.h": "c", + "avformat.h": "c" + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..18851bc --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) + +project(xmedia) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2 -Wall -D_XUTILS_DEBUG") + +include_directories(${PROJECT_SOURCE_DIR}/src) + +set(SOURCES + ${PROJECT_SOURCE_DIR}/src/codec.c + ${PROJECT_SOURCE_DIR}/src/decoder.c + ${PROJECT_SOURCE_DIR}/src/encoder.c + ${PROJECT_SOURCE_DIR}/src/frame.c + ${PROJECT_SOURCE_DIR}/src/meta.c + ${PROJECT_SOURCE_DIR}/src/mpegts.c + ${PROJECT_SOURCE_DIR}/src/nalu.c + ${PROJECT_SOURCE_DIR}/src/status.c + ${PROJECT_SOURCE_DIR}/src/stream.c + ${PROJECT_SOURCE_DIR}/src/version.c +) + +add_library(${PROJECT_NAME} STATIC ${SOURCES}) +SET(DST_DIR "/usr/local/include/xmedia") + +target_link_libraries(${PROJECT_NAME} + xutils + pthread + avutil + avcodec + avformat + avdevice + swscale + swresample +) + +install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION /usr/local/lib) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/ DESTINATION ${DST_DIR} FILES_MATCHING PATTERN "*.h") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..20618dd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sandro Kalatozishvili (s.kalatoz@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..73be6e3 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +#################################### +# Automatically generated by SMake # +# /~https://github.com/kala13x/smake # +#################################### + +CFLAGS = -g -O2 -Wall -D_XUTILS_DEBUG +CFLAGS += -I./src +LIBS = -lxutils -lpthread -lavutil -lavcodec -lavformat -lavdevice -lswscale -lswresample +NAME = libxmedia.a +ODIR = ./build +OBJ = o + +OBJS = codec.$(OBJ) \ + decoder.$(OBJ) \ + encoder.$(OBJ) \ + frame.$(OBJ) \ + meta.$(OBJ) \ + mpegts.$(OBJ) \ + nalu.$(OBJ) \ + status.$(OBJ) \ + stream.$(OBJ) \ + version.$(OBJ) + +OBJECTS = $(patsubst %,$(ODIR)/%,$(OBJS)) +INSTALL_INC = /usr/local/include/xmedia +INSTALL_BIN = /usr/local/lib +VPATH = ./src + +.c.$(OBJ): + @test -d $(ODIR) || mkdir -p $(ODIR) + $(CC) $(CFLAGS) -c -o $(ODIR)/$@ $< $(LIBS) + +$(NAME):$(OBJS) + $(AR) rcs -o $(ODIR)/$(NAME) $(OBJECTS) + +.PHONY: install +install: + @test -d $(INSTALL_BIN) || mkdir -p $(INSTALL_BIN) + install -m 0755 $(ODIR)/$(NAME) $(INSTALL_BIN)/ + @test -d $(INSTALL_INC) || mkdir -p $(INSTALL_INC) + cp -r ./src/*.h $(INSTALL_INC)/ + +.PHONY: clean +clean: + $(RM) $(ODIR)/$(NAME) $(OBJECTS) diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f346dc --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +[![MIT License](https://img.shields.io/badge/License-MIT-brightgreen.svg?)](/~https://github.com/kala13x/libxmedia/blob/main/LICENSE) +[![C/C++ CI](/~https://github.com/kala13x/libxmedia/actions/workflows/make.yml/badge.svg)](/~https://github.com/kala13x/libxmedia/actions/workflows/make.yml) +[![CMake](/~https://github.com/kala13x/libxmedia/actions/workflows/cmake.yml/badge.svg)](/~https://github.com/kala13x/libxmedia/actions) +[![SMake](/~https://github.com/kala13x/libxmedia/actions/workflows/smake.yml/badge.svg)](/~https://github.com/kala13x/libxmedia/actions/workflows/smake.yml) + +# libxmedia +Implementation of audio/video transmuxing functionality based on FFMPEG API with a fully functional example. + +### Installation +There are several ways to build and install the project but at first, the [libxutils](/~https://github.com/kala13x/libxutils) library and the `FFMPEG` development packages must be installed on the system. + +#### Using included script +The simplest way to build and install a project is to use the included build script: + +```bash +git clone /~https://github.com/kala13x/libxmedia.git && ./libxmedia/build.sh --install +``` + +List options that build script supports: + +- `--tool=` Specify `Makefile` generation tool or use included `Makefile`. +- `--install` Install library and the tools after the build. +- `--cleanup` Cleanup object files after build/installation. +- `--example` Include example in the build. + +You can either choose `cmake`, `smake` or `make` as the tool argument, but `cmake` is recommended on platforms other than the Linux. +If the tool will not be specified the script will use `make` (included Makefile) as default. + +#### Using CMake +If you have a `CMake` tool installed in your operating system, here is how project can be built and installed using `cmake`: + +```bash +git clone /~https://github.com/kala13x/libxmedia.git +cd libxmedia +cmake . && make +sudo make install +``` + +#### Using SMake +[SMake](/~https://github.com/kala13x/smake) is a simple Makefile generator tool for the Linux/Unix operating systems: + +```bash +git clone /~https://github.com/kala13x/libxmedia.git +cd libxmedia +smake && make +sudo make install +``` + +#### Using Makefile +The project can also be built with a pre-generated `Makefile` for the Linux. + +```bash +git clone /~https://github.com/kala13x/libxmedia.git +cd libxmedia +make +sudo make install +``` diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..ca33483 --- /dev/null +++ b/build.sh @@ -0,0 +1,147 @@ +#!/bin/bash +# This source is part of "libxmedia" project +# 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + +PROJ_PATH=$(dirname $(readlink -f "$0")) +EXAMPLE_PATH=$PROJ_PATH/tools +LIBRARY_PATH=$PROJ_PATH + +MAKE_TOOL="make" +EXAMPLE_DONE=0 +LIBRARY_DONE=0 +CPU_COUNT=1 + +for arg in "$@"; do + if [[ $arg == --tool=* ]]; then + MAKE_TOOL="${arg#*=}" + echo "Using tool: $MAKE_TOOL" + fi +done + +update_cpu_count() { + if [ $OSTYPE == linux-gnu ]; then + CPU_COUNT=$(nproc) + fi +} + +prepare_build() { + if [[ $MAKE_TOOL == "cmake" ]]; then + if [[ -f $PROJ_PATH/misc/cmake.tmp ]]; then + cp $PROJ_PATH/misc/cmake.tmp $PROJ_PATH/CMakeLists.txt + else + echo "CMakeLists.txt template file is not found!" + exit 1; + fi + elif [[ $MAKE_TOOL == "smake" ]]; then + if [[ -f $PROJ_PATH/misc/smake.tmp ]]; then + cp $PROJ_PATH/misc/smake.tmp $PROJ_PATH/smake.json + else + echo "SMake template file is not found!" + exit 1; + fi + elif [[ $MAKE_TOOL == "make" ]]; then + if [[ -f $PROJ_PATH/misc/make.tmp ]]; then + cp $PROJ_PATH/misc/make.tmp $PROJ_PATH/Makefile + else + echo "Makefile template is not found!" + exit 1; + fi + else + echo "Unknown build tool: $MAKE_TOOL" + echo "Specify cmake, smake or make)" + exit 1 + fi +} + +build_example() { + [ "$EXAMPLE_DONE" -eq 1 ] && return + cd $PROJ_PATH/example + + if [[ $MAKE_TOOL == "cmake" ]]; then + mkdir -p build && cd build + cmake .. && make -j $CPU_COUNT + EXAMPLE_PATH=$PROJ_PATH/example/build + elif [[ $MAKE_TOOL == "smake" ]]; then + smake && make -j $CPU_COUNT + EXAMPLE_PATH=$PROJ_PATH/example + elif [[ $MAKE_TOOL == "make" ]]; then + make -j $CPU_COUNT + EXAMPLE_PATH=$PROJ_PATH/example + fi + + EXAMPLE_DONE=1 +} + +build_lbrary() { + [ "$LIBRARY_DONE" -eq 1 ] && return + cd $PROJ_PATH + + if [[ $MAKE_TOOL == "cmake" ]]; then + mkdir -p build && cd build + cmake .. && make -j $CPU_COUNT + LIBRARY_PATH=$PROJ_PATH/build + elif [[ $MAKE_TOOL == "smake" ]]; then + smake && make -j $CPU_COUNT + LIBRARY_PATH=$PROJ_PATH + elif [[ $MAKE_TOOL == "make" ]]; then + make -j $CPU_COUNT + LIBRARY_PATH=$PROJ_PATH + fi + + LIBRARY_DONE=1 +} + +install_library() { + build_lbrary + cd $LIBRARY_PATH + sudo make install +} + +install_example() { + build_example + cd $EXAMPLE_PATH + sudo make install +} + +clean_path() { + cd "$@" + [ -f ./Makefile ] && make clean + [ -d ./obj ] && rm -rf ./obj + [ -d ./build ] && rm -rf ./build + [ -d ./CMakeFiles ] && rm -rf ./CMakeFiles + [ -f ./CMakeCache.txt ] && rm -rf ./CMakeCache.txt + [ -f ./cmake_install.cmake ] && rm -rf ./cmake_install.cmake + [ -f ./install_manifest.txt ] && rm -rf ./install_manifest.txt + cd $PROJ_PATH +} + +clean_project() { + clean_path $PROJ_PATH + clean_path $PROJ_PATH/example +} + +update_cpu_count +prepare_build +clean_project +build_lbrary + +for arg in "$@"; do + if [[ $arg == "--example" ]]; then + build_example + fi + + if [[ $arg == "--install" ]]; then + install_library + install_example + fi +done + +# Do cleanup last +for arg in "$@"; do + if [[ $arg == "--cleanup" || + $arg == "--clean" ]]; then + clean_project + fi +done + +exit 0 diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..b9a5fd3 --- /dev/null +++ b/clean.sh @@ -0,0 +1,17 @@ +#!/bin/bash +PROJ_PATH=$(dirname $(readlink -f "$0")) + +clean_path() { + cd "$@" + [ -f ./Makefile ] && make clean + [ -d ./obj ] && rm -rf ./obj + [ -d ./build ] && rm -rf ./build + [ -d ./CMakeFiles ] && rm -rf ./CMakeFiles + [ -f ./CMakeCache.txt ] && rm -rf ./CMakeCache.txt + [ -f ./cmake_install.cmake ] && rm -rf ./cmake_install.cmake + [ -f ./install_manifest.txt ] && rm -rf ./install_manifest.txt + cd $PROJ_PATH +} + +clean_path $PROJ_PATH +clean_path $PROJ_PATH/example \ No newline at end of file diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..28ff351 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.10) + +project(xmedia) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2 -Wall -D_XUTILS_DEBUG") + +set(SOURCE_FILES + xmedia.c +) + +set(XMEDIA_LIBRARY_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../build) +set(XMEDIA_STATIC_LIB ${XMEDIA_LIBRARY_ROOT}/libxmedia.a) + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) +target_include_directories(${PROJECT_NAME} PRIVATE ${XMEDIA_LIBRARY_ROOT}/../src) + +# Linking static and shared libraries +target_link_libraries(${PROJECT_NAME} + PRIVATE + ${XMEDIA_STATIC_LIB} + xutils + pthread + avutil + avcodec + avformat + avdevice + swscale + swresample +) + +# Installation rules +install(TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION /usr/local/bin +) + +# Clean up build directory +set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES + "${CMAKE_BINARY_DIR}/build" +) diff --git a/example/Makefile b/example/Makefile new file mode 100644 index 0000000..77dc308 --- /dev/null +++ b/example/Makefile @@ -0,0 +1,33 @@ +#################################### +# Automatically generated by SMake # +# /~https://github.com/kala13x/smake # +#################################### + +CFLAGS = -g -O2 -Wall -D_XUTILS_DEBUG +CFLAGS += -I../src +LD_LIBS = ../build/libxmedia.a +LIBS = -lxutils -lpthread -lavutil -lavcodec -lavformat -lavdevice -lswscale -lswresample +NAME = xmedia +ODIR = ./build +OBJ = o + +OBJS = xmedia.$(OBJ) +OBJECTS = $(patsubst %,$(ODIR)/%,$(OBJS)) +INSTALL_BIN = /usr/local/bin +VPATH = . + +.c.$(OBJ): + @test -d $(ODIR) || mkdir -p $(ODIR) + $(CC) $(CFLAGS) -c -o $(ODIR)/$@ $< $(LIBS) + +$(NAME):$(OBJS) + $(CC) $(CFLAGS) -o $(ODIR)/$(NAME) $(OBJECTS) $(LD_LIBS) $(LIBS) + +.PHONY: install +install: + @test -d $(INSTALL_BIN) || mkdir -p $(INSTALL_BIN) + install -m 0755 $(ODIR)/$(NAME) $(INSTALL_BIN)/ + +.PHONY: clean +clean: + $(RM) $(ODIR)/$(NAME) $(OBJECTS) diff --git a/example/smake.json b/example/smake.json new file mode 100644 index 0000000..3837e1a --- /dev/null +++ b/example/smake.json @@ -0,0 +1,29 @@ +{ + "build": { + "libs": "-lxutils -lpthread -lavutil -lavcodec -lavformat -lavdevice -lswscale -lswresample", + "flags": "-g -O2 -Wall -D_XUTILS_DEBUG", + "ldLibs": "../build/libxmedia.a", + "outputDir": "./build", + "overwrite": true, + "name": "xmedia", + "cxx": false, + "verbose": 2, + + "includes": [ + "../src" + ], + + "excludes": [ + "./libxutils", + "./example", + "./build", + "./smake", + "./cmake", + "./misc" + ] + }, + + "install": { + "binaryDir": "/usr/local/bin" + } +} diff --git a/example/xmedia.c b/example/xmedia.c new file mode 100644 index 0000000..d48b87e --- /dev/null +++ b/example/xmedia.c @@ -0,0 +1,732 @@ +/*! + * @file libxmedia/example/xmedia.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio transcoder + * functionality based on the FFMPEG API and libraries. + */ + +#include "stdinc.h" +#include "version.h" +#include "encoder.h" +#include "decoder.h" +#include "frame.h" +#include "meta.h" + +extern char *optarg; + +typedef struct { + enum AVSampleFormat sampleFmt; + enum AVPixelFormat pixelFmt; + enum AVCodecID videoCodec; + enum AVCodecID audioCodec; + xscale_fmt_t scaleFmt; + + char inputFile[XPATH_MAX]; + char inputFmt[XPATH_MAX]; + char outFile[XPATH_MAX]; + char outFmt[XSTR_TINY]; + + int nWidth; + int nHeight; + + AVRational frameRate; + int nSampleRate; + int nChannels; + + xpts_ctl_t eTSType; + size_t nIOBuffSize; + xbool_t bCustomIO; + xbool_t bRemux; + xbool_t bDebug; + xbool_t bLoop; + size_t nTSFix; +} xmedia_args_t; + +typedef struct { + xmedia_args_t args; + xdecoder_t decoder; + xencoder_t encoder; + xarray_t streams; + xmeta_t meta; + FILE *pFile; +} xtranscoder_t; + +static int g_nInterrupted = 0; + +static void signal_callback(int sig) +{ + if (sig == SIGINT) printf("\n"); + g_nInterrupted = 1; +} + +static void status_cb(void *pUserCtx, xstatus_type_t nType, const char *pStatus) +{ + XASSERT_VOID(pStatus); + if (nType == XSTATUS_ERROR) xloge("%s", pStatus); + else if (nType == XSTATUS_DEBUG) xlogd("%s", pStatus); + else xlogn("%s", pStatus); +} + +static void clear_cb(xarray_data_t *pArrData) +{ + XASSERT_VOID_RET(pArrData); + free(pArrData->pData); +} + +static int muxer_cb(void *pCtx, uint8_t *pData, int nSize) +{ + xlogd("Muxer callback: size(%d)", nSize); + xtranscoder_t *pTransmuxer = (xtranscoder_t*)pCtx; + XASSERT((pTransmuxer->pFile != NULL), XSTDERR); + return fwrite(pData, 1, nSize, pTransmuxer->pFile); +} + +static int encoder_cb(void *pCtx, AVPacket *pPacket) +{ + xlogd("Encoder callback: stream(%d), size(%d), pts(%lld)", + pPacket->stream_index, pPacket->size, pPacket->pts); + return XSTDOK; +} + +static int decoder_cb(void *pCtx, AVFrame *pFrame, int nStreamIndex) +{ + xlogd("Decoder callback: stream(%d), size(%d), pts(%lld)", + nStreamIndex, pFrame->pkt_size, pFrame->pts); + + xtranscoder_t *pTransmuxer = (xtranscoder_t*)pCtx; + xencoder_t *pDecoder = (xencoder_t*)&pTransmuxer->decoder; + xencoder_t *pEncoder = (xencoder_t*)&pTransmuxer->encoder; + + xstream_t *pStream = XStreams_GetBySrcIndex(&pDecoder->streams, nStreamIndex); + XASSERT(pStream, xthrow("Source stream is not found: src(%d)", nStreamIndex)); + XASSERT((pStream->nDstIndex >= 0), xthrow("Output stream is not found: src(%d)", nStreamIndex)); + + return XEncoder_WriteFrame3(pEncoder, pFrame, pStream->nDstIndex); +} + +void XTranscoder_Init(xtranscoder_t *pTransmuxer) +{ + XArray_Init(&pTransmuxer->streams, XSTDNON, XFALSE); + pTransmuxer->streams.clearCb = clear_cb; + + pTransmuxer->args.videoCodec = AV_CODEC_ID_NONE; + pTransmuxer->args.audioCodec = AV_CODEC_ID_NONE; + pTransmuxer->args.sampleFmt = AV_SAMPLE_FMT_NONE; + pTransmuxer->args.pixelFmt = AV_PIX_FMT_NONE; + pTransmuxer->args.scaleFmt = XSCALE_FMT_ASPECT; + + pTransmuxer->args.nWidth = XSTDNON; + pTransmuxer->args.nHeight = XSTDNON; + + pTransmuxer->args.nChannels = XSTDERR; + pTransmuxer->args.nSampleRate = XSTDERR; + pTransmuxer->args.frameRate.num = XSTDERR; + pTransmuxer->args.frameRate.den = XSTDERR; + + xstrnul(pTransmuxer->args.inputFile); + xstrnul(pTransmuxer->args.inputFmt); + xstrnul(pTransmuxer->args.outFile); + xstrnul(pTransmuxer->args.outFmt); + + pTransmuxer->args.nIOBuffSize = XSTDNON; + pTransmuxer->args.bCustomIO = XFALSE; + pTransmuxer->args.eTSType = XPTS_RESCALE; + pTransmuxer->args.nTSFix = XSTDNON; + pTransmuxer->args.bRemux = XFALSE; + pTransmuxer->args.bDebug = XFALSE; + pTransmuxer->args.bLoop = XFALSE; + + avdevice_register_all(); + avformat_network_init(); + + XDecoder_Init(&pTransmuxer->decoder); + XEncoder_Init(&pTransmuxer->encoder); + XMeta_Init(&pTransmuxer->meta); + pTransmuxer->pFile = NULL; +} + +void XTranscoder_Destroy(xtranscoder_t *pTransmuxer) +{ + XEncoder_Destroy(&pTransmuxer->encoder); + XDecoder_Destroy(&pTransmuxer->decoder); + XArray_Destroy(&pTransmuxer->streams); + XMeta_Clear(&pTransmuxer->meta); + avformat_network_deinit(); + + if (pTransmuxer->pFile) + { + fclose(pTransmuxer->pFile); + pTransmuxer->pFile = NULL; + } +} + +xbool_t XTranscoder_InitDecoder(xtranscoder_t *pTransmuxer) +{ + xbool_t bDemuxOnly = pTransmuxer->args.bRemux; + const char *pInFmt = pTransmuxer->args.inputFmt; + const char *pInput = pTransmuxer->args.inputFile; + const char *pFmt = xstrused(pInFmt) ? pInFmt : NULL; + + /* Setup decoder callbacks */ + pTransmuxer->decoder.frameCallback = decoder_cb; + pTransmuxer->decoder.pUserCtx = pTransmuxer; + pTransmuxer->decoder.bDemuxOnly = bDemuxOnly; + pTransmuxer->decoder.status.cb = status_cb; + pTransmuxer->decoder.status.nTypes = XSTATUS_ALL; + + /* Open input file for decoding */ + XSTATUS nStatus = XDecoder_OpenInput(&pTransmuxer->decoder, pInput, pFmt); + return nStatus <= 0 ? XFALSE : XTRUE; +} + +xbool_t XTranscoder_InitEncoder(xtranscoder_t *pTransmuxer) +{ + xarray_t *pSrcStreams = &pTransmuxer->decoder.streams; + xarray_t *pDstStreams = &pTransmuxer->encoder.streams; + + size_t i, nStreamCount = XStreams_GetCount(pSrcStreams); + XASSERT(nStreamCount, xthrowr(XFALSE, "There is no input streams")); + + const char *pOutput = pTransmuxer->args.outFile; + const char *pOutFmt = pTransmuxer->args.outFmt; + const char *pFmt = xstrused(pOutFmt) ? pOutFmt : NULL; + + xpts_ctl_t eTSType = pTransmuxer->args.eTSType; + xbool_t bMuxOnly = pTransmuxer->args.bRemux; + int nTSFix = (int)pTransmuxer->args.nTSFix; + AVDictionary *pMuxOpts = NULL; + + /* Setup encoder and muxer callbacks */ + if (pTransmuxer->args.bCustomIO) + pTransmuxer->encoder.muxerCallback = muxer_cb; + + /* Setup error and info callbacks */ + pTransmuxer->encoder.status.cb = status_cb; + pTransmuxer->encoder.status.nTypes = XSTATUS_ALL; + pTransmuxer->encoder.packetCallback = encoder_cb; + pTransmuxer->encoder.pUserCtx = pTransmuxer; + pTransmuxer->encoder.bMuxOnly = bMuxOnly; + pTransmuxer->encoder.eTSType = eTSType; + pTransmuxer->encoder.nTSFix = nTSFix; + + /* Initialize streams and output format context */ + XSTATUS nStatus = XEncoder_OpenFormat(&pTransmuxer->encoder, pFmt, pOutput); + if (nStatus <= 0) return XFALSE; + + for (i = 0; i < nStreamCount; i++) + { + xcodec_t codecInfo; + XCodec_Init(&codecInfo); + + xstream_t *pSrcStream = XStreams_GetByIndex(pSrcStreams, i); + if (pSrcStream == NULL || pSrcStream->nSrcIndex < 0) continue; + + /* Get codec information from input stream */ + nStatus = XStream_CopyCodecInfo(pSrcStream, &codecInfo); + if (nStatus < 0) return XFALSE; + + /* Discard codec change if only remuxing */ + if (!bMuxOnly) + { + /* Setup any required video/audio codec parameters for transcoding */ + if (codecInfo.mediaType == AVMEDIA_TYPE_VIDEO) + { + if (pTransmuxer->args.scaleFmt != XSCALE_FMT_NONE) + codecInfo.scaleFmt = pTransmuxer->args.scaleFmt; + + if (pTransmuxer->args.videoCodec != AV_CODEC_ID_NONE) + codecInfo.codecId = pTransmuxer->args.videoCodec; + + if (pTransmuxer->args.pixelFmt != AV_PIX_FMT_NONE) + codecInfo.pixFmt = pTransmuxer->args.pixelFmt; + + if (pTransmuxer->args.nWidth > 0 && + pTransmuxer->args.nHeight > 0) + { + codecInfo.nWidth = pTransmuxer->args.nWidth; + codecInfo.nHeight = pTransmuxer->args.nHeight; + } + + if (pTransmuxer->args.frameRate.num > 0 && + pTransmuxer->args.frameRate.den > 0) + { + codecInfo.frameRate = pTransmuxer->args.frameRate; + codecInfo.timeBase = av_inv_q(codecInfo.frameRate); + } + } + else if (codecInfo.mediaType == AVMEDIA_TYPE_AUDIO) + { + if (pTransmuxer->args.audioCodec != AV_CODEC_ID_NONE) + codecInfo.codecId = pTransmuxer->args.audioCodec; + + if (pTransmuxer->args.sampleFmt != AV_SAMPLE_FMT_NONE) + codecInfo.sampleFmt = pTransmuxer->args.sampleFmt; + + if (pTransmuxer->args.nChannels > 0) + XCodec_InitChannels(&codecInfo, pTransmuxer->args.nChannels); + + if (pTransmuxer->args.nSampleRate > 0) + { + codecInfo.nSampleRate = pTransmuxer->args.nSampleRate; + codecInfo.timeBase = (AVRational){1, codecInfo.nSampleRate}; + } + } + } + + /* Open codec for output stream */ + int nDstIndex = XEncoder_OpenStream(&pTransmuxer->encoder, &codecInfo); + + XCodec_Clear(&codecInfo); + if (nDstIndex < 0) return XFALSE; + + xstream_t *pDstStream = XStreams_GetByDstIndex(pDstStreams, nDstIndex); + XASSERT(pDstStream, xthrowr(XFALSE, "Failed to get dst stream: %d", nDstIndex)); + + /* Stream I/O mapping */ + pDstStream->nSrcIndex = pSrcStream->nSrcIndex; + pSrcStream->nDstIndex = pDstStream->nDstIndex; + } + + if (!XStreams_GetCount(pDstStreams)) + { + xloge("Oputput streams is not initialized"); + return XFALSE; + } + + /* Open output file handle */ + if (pTransmuxer->args.bCustomIO) + { + pTransmuxer->pFile = fopen(pOutput, "wb"); + if (pTransmuxer->pFile == NULL) + { + xloge("Failed to open output: %s (%s)", pOutput, strerror(errno)); + return XFALSE; + } + + /* Initialize muxer */ + if (xstrncmp(pOutFmt, "mp4", 3)) + { + /* Create a new fragment for each keyframe (for smooth streaming) */ + av_dict_set(&pMuxOpts, "movflags", "frag_keyframe+empty_moov", 0); + } + else if (xstrncmp(pOutFmt, "mpegts", 6)) + { + char sPeriod[XSTR_TINY]; /* Add PAT/PMT for each keyframe */ + snprintf(sPeriod, sizeof(sPeriod), "%d", (INT_MAX / 2) - 1); + av_dict_set(&pMuxOpts, "sdt_period", sPeriod, 0); + av_dict_set(&pMuxOpts, "pat_period", sPeriod, 0); + } + } + + /* Setup metadta and encoder user input variables */ + XEncoder_AddMeta(&pTransmuxer->encoder, &pTransmuxer->meta); + pTransmuxer->encoder.nIOBuffSize = pTransmuxer->args.nIOBuffSize; + + /* Open encder IO and setup write callback */ + nStatus = XEncoder_OpenOutput(&pTransmuxer->encoder, pMuxOpts); + XASSERT((nStatus > 0), xthrowr(XFALSE, "Failed to open output: %s", pOutput)); + + return XTRUE; +} + +xbool_t XTranscoder_Transcode(xtranscoder_t *pTransmuxer) +{ + /* Allocate AVPacket for decoding */ + AVPacket *pPacket = av_packet_alloc(); + if (pPacket == NULL) + { + xloge("Failed to allocate packet: %s", strerror(errno)); + return XFALSE; + } + + /* Read packet from the input */ + xbool_t bRemux = pTransmuxer->args.bRemux; + XSTATUS nStatus = XDecoder_ReadPacket(&pTransmuxer->decoder, pPacket); + + while (!g_nInterrupted && nStatus >= 0) + { + if (bRemux) XEncoder_WritePacket(&pTransmuxer->encoder, pPacket); + else XDecoder_DecodePacket(&pTransmuxer->decoder, pPacket); + av_packet_unref(pPacket); /* Recycle packet */ + + nStatus = XDecoder_ReadPacket(&pTransmuxer->decoder, pPacket); + if (nStatus == AVERROR_EOF && pTransmuxer->args.bLoop) + { + int nStream = pPacket->stream_index; + xlogd("Seeking input stream to the start position: index(%d)", nStream); + + nStatus = XDecoder_Seek(&pTransmuxer->decoder, nStream, 0, AVSEEK_FLAG_BACKWARD); + if (nStatus < 0) xloge("Failed to seek stream: %d (%d)", nStream, nStatus); + } + } + + /* Finish encoding and destroy context */ + XEncoder_FinishWrite(&pTransmuxer->encoder, !bRemux); + av_packet_free(&pPacket); + return XSTDOK; +} + +void XTranscoder_Usage(const char *pName) +{ + xlog("================================================================"); + xlog(" Transcoder implementation example - %s", XMedia_Version()); + xlog("================================================================"); + xlog("Usage: %s [options]\n", pName); + xlog("Options are:"); + xlog(" -i # Input file or sream path (%s*%s)", XSTR_CLR_RED, XSTR_FMT_RESET); + xlog(" -o # Output file or srewam path (%s*%s)", XSTR_CLR_RED, XSTR_FMT_RESET); + xlog(" -e # Input format name (example: v4l2)"); + xlog(" -f # Output format name (example: mp4)"); + xlog(" -x # Video scale format (example: aspect)"); + xlog(" -p # Video pixel format (example: yuv420p)"); + xlog(" -s # Audio sample format (example: s16p)"); + xlog(" -k # Video frame rate (example: 90000:3000)"); + xlog(" -q # Audio sample rate (example: 48000)"); + xlog(" -c # Audio channel count (example: 2)"); + xlog(" -v # Output video codec (example: h264)"); + xlog(" -a # Output audio codec (example: mp3)"); + xlog(" -w # Output video width (example: 1280)"); + xlog(" -h # Output video height (example: 720)"); + xlog(" -b # IO buffer size (default: 65536)"); + xlog(" -t # Timestamp calculation type"); + xlog(" -m # Metadata file path"); + xlog(" -n # Fix non motion PTS/DTS"); + xlog(" -z # Custom output handling"); + xlog(" -l # Loop transcoding/remuxing"); + xlog(" -r # Remux only"); + xlog(" -d # Debug logs"); + xlog(" -u # Usage information\n"); + + xlog("Video scale formats:"); + xlog("- stretch %s(Stretch video frames to the given resolution)%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("- aspect %s(Scale video frames and protect aspect ratio)%s\n", XSTR_FMT_DIM, XSTR_FMT_RESET); + + xlog("Timestamp calculation types:"); + xlog("- calculate %s(Calculate TS based on the elapsed time and clock rate)%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("- compute %s(Compute TS based on the sample rate and time base)%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("- rescale %s(Rescale original TS using av_packet_rescale_ts())%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("- round %s(Rescale original TS and round to the nearest value)%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("- source %s(Use original PTS from the source stream)%s\n", XSTR_FMT_DIM, XSTR_FMT_RESET); + + xlog("Metadata file syntax:"); + xlog("%sstart-time|end-time|chapter-name%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("%sfield-name|field-string%s\n", XSTR_FMT_DIM, XSTR_FMT_RESET); + + xlog("If the line consists of three sections, it will be parsed"); + xlog("as a chapter, if it consists of two sections as metadata."); + xlog("hh:mm:ss time format is used for chapter start/end time.\n"); + + xlog("Metadata file example:"); + xlog("%s00:00:00|00:00:40|Opening chapter%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("%s00:00:40|00:10:32|Another chapter%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("%s00:10:32|00:15:00|Final chapter%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("%sComment|Created with xmedia%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("%sTitle|Example meta%s", XSTR_FMT_DIM, XSTR_FMT_RESET); + xlog("%sAlbum|Examples%s\n", XSTR_FMT_DIM, XSTR_FMT_RESET); + + xlog("Examples:"); + xlog("%s%s -i file.avi -o encoded.mp4 -f mp4%s", XSTR_FMT_DIM, pName, XSTR_FMT_RESET); + xlog("%s%s -i file.mp4 -ro remuxed.mp4 -f mp4 -m meta.txt%s\n", XSTR_FMT_DIM, pName, XSTR_FMT_RESET); +} + +xpts_ctl_t XTranscoder_GetTSType(const char *pType) +{ + if (!strncmp(pType, "calculate", 9)) return XPTS_CALCULATE; + else if (!strncmp(pType, "compute", 7)) return XPTS_COMPUTE; + else if (!strncmp(pType, "rescale", 7)) return XPTS_RESCALE; + else if (!strncmp(pType, "round", 5)) return XPTS_ROUND; + else if (!strncmp(pType, "source", 6)) return XPTS_SOURCE; + return XPTS_INVALID; +} + +AVRational XTranscoder_GetFrameRate(const char *pFrameRate) +{ + AVRational frameRate; + frameRate.num = XSTDERR; + frameRate.den = XSTDERR; + + if (xstrsrc(pFrameRate, ":") <= 0) return frameRate; + xarray_t *tokens = xstrsplit(pFrameRate, ":"); + + if (tokens->nUsed != 2) + { + XArray_Destroy(tokens); + return frameRate; + } + + char *pNum = (char*)XArray_GetData(tokens, 0); + char *pDen = (char*)XArray_GetData(tokens, 1); + + if (!xstrused(pNum) || !xstrused(pDen)) + { + XArray_Destroy(tokens); + return frameRate; + } + + frameRate.num = atoi(pNum); + frameRate.den = atoi(pDen); + + XArray_Destroy(tokens); + return frameRate; +} + +xbool_t XTranscoder_ParseMeta(xtranscoder_t *pTransmuxer, const char *pInput) +{ + xmeta_t *pMeta = &pTransmuxer->meta; + size_t nMetaLength = 0; + + char *pMetaRaw = (char*)XPath_Load(pInput, &nMetaLength); + XASSERT(pMetaRaw, xthrowr(XFALSE, "Failed to parse metadata file: %s", strerror(errno))); + + char *pSavePtr = NULL; + char *pLine = xstrtok(pMetaRaw, "\n", &pSavePtr); + + while (pLine != NULL) + { + xarray_t *pTokens = xstrsplit(pLine, "|"); + pLine = xstrtok(NULL, "\n", &pSavePtr); + if (pTokens == NULL) continue; + + if (pTokens->nUsed == 3) + { + char *pStartTime = (char*)XArray_GetData(pTokens, 0); + char *pEndTime = (char*)XArray_GetData(pTokens, 1); + char *pTitle = (char*)XArray_GetData(pTokens, 2); + XMeta_AddChapterTime(pMeta, pStartTime, pEndTime, pTitle); + } + else if (pTokens->nUsed) + { + char *pFieldName = (char*)XArray_GetData(pTokens, 0); + char *pFieldData = (char*)XArray_GetData(pTokens, 1); + XMeta_AddField(pMeta, pFieldName, pFieldData); + } + + XArray_Destroy(pTokens); + } + + free(pMetaRaw); + return XSTDOK; +} + +XSTATUS XTranscoder_ParseArgs(xtranscoder_t *pTransmuxer, int argc, char *argv[]) +{ + xmedia_args_t *pArgs = (xmedia_args_t*)&pTransmuxer->args; + char sFrameRate[XSTR_TINY]; + char sMetaData[XPATH_MAX]; + char sTSType[XSTR_TINY]; + + xstrnul(sFrameRate); + xstrnul(sMetaData); + xstrnul(sTSType); + int nChar = 0; + + xbool_t bAudioCodecParsed = XFALSE; + xbool_t bVideoCodecParsed = XFALSE; + xbool_t bScaleFormatParsed = XFALSE; + xbool_t bPixelFormatParsed = XFALSE; + xbool_t bSampleFormatParsed = XFALSE; + + while ((nChar = getopt(argc, argv, "a:b:c:f:i:e:m:n:o:p:k:q:s:t:w:h:v:x:z1:l1:d1:r1:u1")) != -1) + { + switch (nChar) + { + case 'i': + xstrncpy(pArgs->inputFile, sizeof(pArgs->inputFile), optarg); + break; + case 'e': + xstrncpy(pArgs->inputFmt, sizeof(pArgs->inputFmt), optarg); + break; + case 'o': + xstrncpy(pArgs->outFile, sizeof(pArgs->outFile), optarg); + break; + case 'f': + xstrncpy(pArgs->outFmt, sizeof(pArgs->outFmt), optarg); + break; + case 'k': + xstrncpy(sFrameRate, sizeof(sFrameRate), optarg); + break; + case 'm': + xstrncpy(sMetaData, sizeof(sMetaData), optarg); + break; + case 't': + xstrncpy(sTSType, sizeof(sTSType), optarg); + break; + case 'a': + pArgs->audioCodec = XCodec_GetIDByName(optarg); + bAudioCodecParsed = XTRUE; + break; + case 'v': + pArgs->videoCodec = XCodec_GetIDByName(optarg); + bVideoCodecParsed = XTRUE; + break; + case 'x': + pArgs->scaleFmt = XFrame_GetScaleFmt(optarg); + bScaleFormatParsed = XTRUE; + break; + case 'p': + pArgs->pixelFmt = av_get_pix_fmt(optarg); + bPixelFormatParsed = XTRUE; + break; + case 's': + pArgs->sampleFmt = av_get_sample_fmt(optarg); + bSampleFormatParsed = XTRUE; + break; + case 'q': + pArgs->nSampleRate = atoi(optarg); + break; + case 'c': + pArgs->nChannels = atoi(optarg); + break; + case 'b': + pArgs->nIOBuffSize = atoi(optarg); + break; + case 'w': + pArgs->nWidth = atoi(optarg); + break; + case 'h': + pArgs->nHeight = atoi(optarg); + break; + case 'n': + pArgs->nTSFix = atoi(optarg); + break; + case 'z': + pArgs->bCustomIO = XTRUE; + break; + case 'r': + pArgs->bRemux = XTRUE; + break; + case 'd': + pArgs->bDebug = XTRUE; + break; + case 'l': + pArgs->bLoop = XTRUE; + break; + case 'u': + return XSTDNON; + default: + return XSTDERR; + } + } + + if (!xstrused(pArgs->inputFile)) + { + xloge("Required input file argument"); + return XSTDERR; + } + + if (!xstrused(pArgs->outFile)) + { + xloge("Required output file argument"); + return XSTDERR; + } + + if (bAudioCodecParsed && pArgs->audioCodec == AV_CODEC_ID_NONE) + { + xloge("Audio codec is not found"); + return XSTDERR; + } + + if (bVideoCodecParsed && pArgs->videoCodec == AV_CODEC_ID_NONE) + { + xloge("Video codec is not found"); + return XSTDERR; + } + + if (bPixelFormatParsed && pArgs->pixelFmt == AV_PIX_FMT_NONE) + { + xloge("Video pixel format is not found"); + return XSTDERR; + } + + if (bSampleFormatParsed && pArgs->sampleFmt == AV_SAMPLE_FMT_NONE) + { + xloge("Audio sample format is not found"); + return XSTDERR; + } + + if (bScaleFormatParsed && pArgs->scaleFmt == XSCALE_FMT_NONE) + { + xloge("Video scale format is not found"); + return XSTDERR; + } + + if (xstrused(sTSType)) + { + pTransmuxer->args.eTSType = XTranscoder_GetTSType(sTSType); + if (pTransmuxer->args.eTSType == XPTS_INVALID) + { + xloge("Invalid PTS rescaling type: %s", sTSType); + return XSTDERR; + } + } + + if (xstrused(sFrameRate)) + { + pTransmuxer->args.frameRate = XTranscoder_GetFrameRate(sFrameRate); + if (pTransmuxer->args.frameRate.num <= 0 || + pTransmuxer->args.frameRate.den <= 0) + { + xloge("Invalid video frame rate: %s", sFrameRate); + return XSTDERR; + } + } + + if (xstrused(sMetaData) && + !XTranscoder_ParseMeta(pTransmuxer, sMetaData)) + return XSTDERR; + + if (pArgs->bDebug) + { + av_log_set_level(AV_LOG_VERBOSE); + xlog_timing(XLOG_TIME); + xlog_enable(XLOG_ALL); + xlog_indent(XTRUE); + } + + return XSTDOK; +} + +int main(int argc, char *argv[]) +{ + /* Initialize logger */ + xlog_defaults(); + xlog_enable(XLOG_INFO); + + /* Initialize FFMPEG logger */ + av_log_set_level(AV_LOG_WARNING); + XSTATUS nStatus = XSTDOK; + + int nSignals[2]; + nSignals[0] = SIGTERM; + nSignals[1] = SIGINT; + XSig_Register(nSignals, 2, signal_callback); + + /* Initialize transcoder */ + xtranscoder_t transcoder; + XTranscoder_Init(&transcoder); + + /* Parse arguments */ + nStatus = XTranscoder_ParseArgs(&transcoder, argc, argv); + if (!nStatus) + { + XTranscoder_Usage(argv[0]); + return XSTDOK; + } + else if (nStatus < 0) + { + xlogi("Run %s with -u for usage info", argv[0]); + return XSTDERR; + } + + /* Init encoder/decoder and start transcoding */ + if (!XTranscoder_InitDecoder(&transcoder) || + !XTranscoder_InitEncoder(&transcoder) || + !XTranscoder_Transcode(&transcoder)) nStatus = XSTDERR; + + /* Cleanup everything */ + XTranscoder_Destroy(&transcoder); + return nStatus; +} \ No newline at end of file diff --git a/misc/cmake.tmp b/misc/cmake.tmp new file mode 100644 index 0000000..18851bc --- /dev/null +++ b/misc/cmake.tmp @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) + +project(xmedia) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O2 -Wall -D_XUTILS_DEBUG") + +include_directories(${PROJECT_SOURCE_DIR}/src) + +set(SOURCES + ${PROJECT_SOURCE_DIR}/src/codec.c + ${PROJECT_SOURCE_DIR}/src/decoder.c + ${PROJECT_SOURCE_DIR}/src/encoder.c + ${PROJECT_SOURCE_DIR}/src/frame.c + ${PROJECT_SOURCE_DIR}/src/meta.c + ${PROJECT_SOURCE_DIR}/src/mpegts.c + ${PROJECT_SOURCE_DIR}/src/nalu.c + ${PROJECT_SOURCE_DIR}/src/status.c + ${PROJECT_SOURCE_DIR}/src/stream.c + ${PROJECT_SOURCE_DIR}/src/version.c +) + +add_library(${PROJECT_NAME} STATIC ${SOURCES}) +SET(DST_DIR "/usr/local/include/xmedia") + +target_link_libraries(${PROJECT_NAME} + xutils + pthread + avutil + avcodec + avformat + avdevice + swscale + swresample +) + +install(TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION /usr/local/lib) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/src/ DESTINATION ${DST_DIR} FILES_MATCHING PATTERN "*.h") diff --git a/misc/make.tmp b/misc/make.tmp new file mode 100644 index 0000000..73be6e3 --- /dev/null +++ b/misc/make.tmp @@ -0,0 +1,45 @@ +#################################### +# Automatically generated by SMake # +# /~https://github.com/kala13x/smake # +#################################### + +CFLAGS = -g -O2 -Wall -D_XUTILS_DEBUG +CFLAGS += -I./src +LIBS = -lxutils -lpthread -lavutil -lavcodec -lavformat -lavdevice -lswscale -lswresample +NAME = libxmedia.a +ODIR = ./build +OBJ = o + +OBJS = codec.$(OBJ) \ + decoder.$(OBJ) \ + encoder.$(OBJ) \ + frame.$(OBJ) \ + meta.$(OBJ) \ + mpegts.$(OBJ) \ + nalu.$(OBJ) \ + status.$(OBJ) \ + stream.$(OBJ) \ + version.$(OBJ) + +OBJECTS = $(patsubst %,$(ODIR)/%,$(OBJS)) +INSTALL_INC = /usr/local/include/xmedia +INSTALL_BIN = /usr/local/lib +VPATH = ./src + +.c.$(OBJ): + @test -d $(ODIR) || mkdir -p $(ODIR) + $(CC) $(CFLAGS) -c -o $(ODIR)/$@ $< $(LIBS) + +$(NAME):$(OBJS) + $(AR) rcs -o $(ODIR)/$(NAME) $(OBJECTS) + +.PHONY: install +install: + @test -d $(INSTALL_BIN) || mkdir -p $(INSTALL_BIN) + install -m 0755 $(ODIR)/$(NAME) $(INSTALL_BIN)/ + @test -d $(INSTALL_INC) || mkdir -p $(INSTALL_INC) + cp -r ./src/*.h $(INSTALL_INC)/ + +.PHONY: clean +clean: + $(RM) $(ODIR)/$(NAME) $(OBJECTS) diff --git a/misc/smake.tmp b/misc/smake.tmp new file mode 100644 index 0000000..6866d3f --- /dev/null +++ b/misc/smake.tmp @@ -0,0 +1,24 @@ +{ + "build": { + "libs": "-lxutils -lpthread -lavutil -lavcodec -lavformat -lavdevice -lswscale -lswresample", + "flags": "-g -O2 -Wall -D_XUTILS_DEBUG", + "outputDir": "./build", + "name": "libxmedia.a", + "overwrite": true, + "verbose": 2, + "cxx": false, + + "excludes": [ + "./libxutils", + "./example", + "./build", + "./smake", + "./cmake" + ] + }, + + "install": { + "headerDir": "/usr/local/include/xmedia", + "binaryDir": "/usr/local/lib" + } +} diff --git a/smake.json b/smake.json new file mode 100644 index 0000000..6866d3f --- /dev/null +++ b/smake.json @@ -0,0 +1,24 @@ +{ + "build": { + "libs": "-lxutils -lpthread -lavutil -lavcodec -lavformat -lavdevice -lswscale -lswresample", + "flags": "-g -O2 -Wall -D_XUTILS_DEBUG", + "outputDir": "./build", + "name": "libxmedia.a", + "overwrite": true, + "verbose": 2, + "cxx": false, + + "excludes": [ + "./libxutils", + "./example", + "./build", + "./smake", + "./cmake" + ] + }, + + "install": { + "headerDir": "/usr/local/include/xmedia", + "binaryDir": "/usr/local/lib" + } +} diff --git a/src/codec.c b/src/codec.c new file mode 100644 index 0000000..6ae8440 --- /dev/null +++ b/src/codec.c @@ -0,0 +1,724 @@ +/*! + * @file libxmedia/src/codec.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio codec + * functionality based on the FFMPEG API and libraries. + */ + +#include "codec.h" + +#define XCODEC_APPLY_RATIONAL(dst, src) if (src.num >= 0 && src.den >= 0) dst = src +#define XCODEC_APPLY_INTEGER(dst, src) if (src >= 0) dst = src + +#define XNAL_UNIT_START_CODE "\x00\x00\x00\x01" +#define XOPUS_HEADER_SIZE 19 + +uint8_t* X264_CreateExtra(x264_extra_t *pExtra, int *pExtraSize) +{ + XASSERT((pExtra && pExtra->pSPS && pExtra->pPPS && + pExtra->nSPSSize && pExtra->nPPSSize), NULL); + + /* Allocate extradata */ + int nExtraDataSize = pExtra->nSPSSize + pExtra->nPPSSize + 8; + uint8_t *pExtraData = av_mallocz(nExtraDataSize); + XASSERT(pExtraData, NULL); + + /* Write NAL unit start code and SPS data */ + memcpy(pExtraData, XNAL_UNIT_START_CODE, 4); + memcpy(&pExtraData[4], pExtra->pSPS, pExtra->nSPSSize); + + /* Write NAL unit start code and PPS data */ + memcpy(&pExtraData[4 + pExtra->nSPSSize], XNAL_UNIT_START_CODE, 4); + memcpy(&pExtraData[8 + pExtra->nSPSSize], pExtra->pPPS, pExtra->nPPSSize); + + /* Reset buffer padding and set extradata size */ + if (pExtraSize) *pExtraSize = nExtraDataSize; + return pExtraData; +} + +uint8_t* XOPUS_CreateExtra(xopus_header_t *pHeader, int *pExtraSize) +{ + /* Allocate extradata */ + uint8_t *pExtraData = av_mallocz(XOPUS_HEADER_SIZE); + XASSERT(pExtraData, NULL); + + /* Set the fixed parts of the header */ + const char sOputsHead[] = "OpusHead"; + memcpy(pExtraData, sOputsHead, 8); + pExtraData[8] = 1; // Version (0 or 1) + + /* Set the variable parts of the header */ + pExtraData[9] = pHeader->nChannels; + *((uint16_t *)(pExtraData + 10)) = pHeader->nPreSkip; + *((uint32_t *)(pExtraData + 12)) = pHeader->nInputSampleRate; + *((int16_t *)(pExtraData + 16)) = pHeader->nOutputGain; + pExtraData[18] = pHeader->nChannelMappingFamily; + + /* The basic OpusHead structure has a size of 19 bytes */ + if (pExtraSize) *pExtraSize = XOPUS_HEADER_SIZE; + return pExtraData; +} + +void XCodec_Init(xcodec_t *pInfo) +{ + XASSERT_VOID(pInfo); + pInfo->mediaType = AVMEDIA_TYPE_UNKNOWN; + pInfo->codecId = AV_CODEC_ID_NONE; + pInfo->nCompressLevel = XCODEC_NOT_SET; + pInfo->nFrameSize = XCODEC_NOT_SET; + pInfo->nProfile = FF_PROFILE_UNKNOWN; + pInfo->timeBase = XRATIONAL_NOT_SET; + pInfo->nBitRate = XCODEC_NOT_SET; + + /* Video codec properties */ + pInfo->aspectRatio = XRATIONAL_NOT_SET; + pInfo->scaleFmt = XSCALE_FMT_STRETCH; + pInfo->pixFmt = AV_PIX_FMT_NONE; + pInfo->nWidth = XCODEC_NOT_SET; + pInfo->nHeight = XCODEC_NOT_SET; + + /* Codec extradata */ + pInfo->nExtraSize = XSTDNON; + pInfo->pExtraData = NULL; + + /* Audio codec properties */ + pInfo->nBitsPerSample = XCODEC_NOT_SET; + pInfo->nSampleRate = XCODEC_NOT_SET; + pInfo->sampleFmt = AV_SAMPLE_FMT_NONE; + pInfo->nChannels = XCODEC_NOT_SET; + +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_default(&pInfo->channelLayout, 0); + pInfo->channelLayout.nb_channels = XCODEC_NOT_SET; +#else + pInfo->nChannelLayout = XCODEC_NOT_SET; +#endif +} + +void XCodec_Clear(xcodec_t *pInfo) +{ + XASSERT_VOID(pInfo); + pInfo->nExtraSize = XSTDNON; + + if (pInfo->pExtraData != NULL) + { + av_free(pInfo->pExtraData); + pInfo->pExtraData = NULL; + } +} + +void XCodec_InitChannels(xcodec_t *pInfo, int nChannels) +{ + pInfo->nChannels = nChannels; + +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_default(&pInfo->channelLayout, nChannels); +#else + if (nChannels == 1) pInfo->nChannelLayout = AV_CH_LAYOUT_MONO; + else pInfo->nChannelLayout = AV_CH_LAYOUT_STEREO; +#endif +} + +void XCodec_CopyChannels(xcodec_t *pDst, const xcodec_t *pSrc) +{ +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_copy(&pDst->channelLayout, &pSrc->channelLayout); + pDst->nChannels = pDst->channelLayout.nb_channels; +#else + pDst->nChannelLayout = pSrc->nChannelLayout; + pDst->nChannels = pSrc->nChannels; +#endif +} + +XSTATUS XCodec_Copy(xcodec_t *pDst, const xcodec_t *pSrc) +{ + XASSERT((pDst && pSrc), XSTDINV); + pDst->mediaType = pSrc->mediaType; + pDst->codecId = pSrc->codecId; + pDst->nProfile = pSrc->nProfile; + pDst->timeBase = pSrc->timeBase; + pDst->nBitRate = pSrc->nBitRate; + pDst->frameRate = pSrc->frameRate; + pDst->nFrameSize = pSrc->nFrameSize; + pDst->nCompressLevel = pSrc->nCompressLevel; + + /* Video codec properties */ + pDst->pixFmt = pSrc->pixFmt; + pDst->aspectRatio = pSrc->aspectRatio; + pDst->scaleFmt = pSrc->scaleFmt; + pDst->nWidth = pSrc->nWidth; + pDst->nHeight = pSrc->nHeight; + + /* Audio codec properties */ + pDst->sampleFmt = pSrc->sampleFmt; + pDst->nSampleRate = pSrc->nSampleRate; + pDst->nBitsPerSample = pSrc->nBitsPerSample; + XCodec_CopyChannels(pDst, pSrc); + + /* Codec context extradata */ + uint8_t *pExtraData = pSrc->pExtraData; + int nExtraSize = pSrc->nExtraSize; + return XCodec_AddExtra(pDst, pExtraData, nExtraSize); +} + +XSTATUS XCodec_ApplyToAVCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx) +{ + XASSERT((pInfo && pCodecCtx), XSTDINV); + + if (pInfo->mediaType == AVMEDIA_TYPE_VIDEO) + return XCodec_ApplyVideoCodec(pInfo, pCodecCtx); + else if (pInfo->mediaType == AVMEDIA_TYPE_AUDIO) + return XCodec_ApplyAudioCodec(pInfo, pCodecCtx); + + return XSTDINV; +} + +XSTATUS XCodec_ApplyToAVParam(xcodec_t *pInfo, AVCodecParameters *pCodecPar) +{ + XASSERT((pInfo && pCodecPar), XSTDINV); + + if (pInfo->mediaType == AVMEDIA_TYPE_VIDEO) + return XCodec_ApplyVideoParam(pInfo, pCodecPar); + else if (pInfo->mediaType == AVMEDIA_TYPE_AUDIO) + return XCodec_ApplyAudioParam(pInfo, pCodecPar); + + return XSTDINV; +} + +XSTATUS XCodec_GetFromAVCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx) +{ + XASSERT((pCodecCtx && pInfo), XSTDINV); + pInfo->mediaType = pCodecCtx->codec_type; + pInfo->nBitRate = pCodecCtx->bit_rate; + pInfo->nProfile = pCodecCtx->profile; + pInfo->codecId = pCodecCtx->codec_id; + pInfo->timeBase = pCodecCtx->time_base; + pInfo->nFrameSize = pCodecCtx->frame_size; + pInfo->nCompressLevel = pCodecCtx->compression_level; + + if (pInfo->mediaType == AVMEDIA_TYPE_VIDEO) + { + /* Video codec properties */ + pInfo->aspectRatio = pCodecCtx->sample_aspect_ratio; + pInfo->frameRate = pCodecCtx->framerate; + pInfo->pixFmt = pCodecCtx->pix_fmt; + pInfo->nWidth = pCodecCtx->width; + pInfo->nHeight = pCodecCtx->height; + } + else if (pInfo->mediaType == AVMEDIA_TYPE_AUDIO) + { + /* Audio codec properties */ + pInfo->nSampleRate = pCodecCtx->sample_rate; + pInfo->sampleFmt = pCodecCtx->sample_fmt; + pInfo->nBitsPerSample = pCodecCtx->bits_per_coded_sample; + +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_copy(&pInfo->channelLayout, &pCodecCtx->ch_layout); + pInfo->nChannels = pInfo->channelLayout.nb_channels; +#else + pInfo->nChannelLayout = pCodecCtx->channel_layout; + pInfo->nChannels = pCodecCtx->channels; +#endif + } + + return XSTDOK; +} + +XSTATUS XCodec_GetFromAVStream(xcodec_t *pInfo, AVStream *pStream) +{ + XASSERT((pStream && pStream->codecpar && pInfo), XSTDINV); + AVCodecParameters *pCodecPar = pStream->codecpar; + + pInfo->mediaType = pCodecPar->codec_type; + pInfo->nBitRate = pCodecPar->bit_rate; + pInfo->nProfile = pCodecPar->profile; + pInfo->codecId = pCodecPar->codec_id; + pInfo->nFrameSize = pCodecPar->frame_size; + + pInfo->timeBase = pStream->time_base; + pInfo->nCompressLevel = XSTDINV; + + if (pInfo->mediaType == AVMEDIA_TYPE_VIDEO) + { + /* Video codec properties */ + pInfo->aspectRatio = pCodecPar->sample_aspect_ratio; + pInfo->frameRate = pStream->avg_frame_rate; + pInfo->pixFmt = pCodecPar->format; + pInfo->nWidth = pCodecPar->width; + pInfo->nHeight = pCodecPar->height; + } + else if (pInfo->mediaType == AVMEDIA_TYPE_AUDIO) + { + /* Audio codec properties */ + pInfo->nSampleRate = pCodecPar->sample_rate; + pInfo->sampleFmt = pCodecPar->format; + pInfo->nBitsPerSample = pCodecPar->bits_per_coded_sample; + +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_copy(&pInfo->channelLayout, &pCodecPar->ch_layout); + pInfo->nChannels = pInfo->channelLayout.nb_channels; +#else + pInfo->nChannelLayout = pCodecPar->channel_layout; + pInfo->nChannels = pCodecPar->channels; +#endif + } + + if (pCodecPar->extradata != NULL && + pCodecPar->extradata_size > 0) + XCodec_GetStreamExtra(pInfo, pStream); + + return XSTDOK; +} + +XSTATUS XCodec_ApplyVideoCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx) +{ + XASSERT((pCodecCtx && pInfo), XSTDINV); + pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO; + + XCODEC_APPLY_RATIONAL(pCodecCtx->sample_aspect_ratio, pInfo->aspectRatio); + XCODEC_APPLY_RATIONAL(pCodecCtx->framerate, pInfo->frameRate); + XCODEC_APPLY_RATIONAL(pCodecCtx->time_base, pInfo->timeBase); + + XCODEC_APPLY_INTEGER(pCodecCtx->compression_level, pInfo->nCompressLevel); + XCODEC_APPLY_INTEGER(pCodecCtx->frame_size, pInfo->nFrameSize); + XCODEC_APPLY_INTEGER(pCodecCtx->profile, pInfo->nProfile); + + XCODEC_APPLY_INTEGER(pCodecCtx->width, pInfo->nWidth); + XCODEC_APPLY_INTEGER(pCodecCtx->height, pInfo->nHeight); + + if (pInfo->codecId != AV_CODEC_ID_NONE) + pCodecCtx->codec_id = pInfo->codecId; + + if (pInfo->pixFmt != AV_PIX_FMT_NONE) + pCodecCtx->pix_fmt = pInfo->pixFmt; + + return XSTDOK; +} + +XSTATUS XCodec_ApplyAudioCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx) +{ + XASSERT((pCodecCtx && pInfo), XSTDINV); + pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO; + + XCODEC_APPLY_INTEGER(pCodecCtx->bits_per_coded_sample, pInfo->nBitsPerSample); + XCODEC_APPLY_INTEGER(pCodecCtx->compression_level, pInfo->nCompressLevel); + XCODEC_APPLY_INTEGER(pCodecCtx->sample_rate, pInfo->nSampleRate); + XCODEC_APPLY_INTEGER(pCodecCtx->frame_size, pInfo->nFrameSize); + XCODEC_APPLY_INTEGER(pCodecCtx->bit_rate, pInfo->nBitRate); + XCODEC_APPLY_INTEGER(pCodecCtx->profile, pInfo->nProfile); + XCODEC_APPLY_RATIONAL(pCodecCtx->time_base, pInfo->timeBase); + + if (pInfo->sampleFmt != AV_SAMPLE_FMT_NONE) + pCodecCtx->sample_fmt = pInfo->sampleFmt; + + if (pInfo->codecId != AV_CODEC_ID_NONE) + pCodecCtx->codec_id = pInfo->codecId; + +#ifdef XCODEC_USE_NEW_CHANNEL + if (pInfo->channelLayout.nb_channels > 0) + av_channel_layout_copy(&pCodecCtx->ch_layout, &pInfo->channelLayout); +#else + XCODEC_APPLY_INTEGER(pCodecCtx->channel_layout, pInfo->nChannelLayout); + XCODEC_APPLY_INTEGER(pCodecCtx->channels, pInfo->nChannels); +#endif + + return XSTDOK; +} + +XSTATUS XCodec_ApplyVideoParam(xcodec_t *pInfo, AVCodecParameters *pCodecPar) +{ + XASSERT((pCodecPar && pInfo), XSTDINV); + pCodecPar->codec_type = AVMEDIA_TYPE_VIDEO; + + XCODEC_APPLY_RATIONAL(pCodecPar->sample_aspect_ratio, pInfo->aspectRatio); + XCODEC_APPLY_INTEGER(pCodecPar->frame_size, pInfo->nFrameSize); + XCODEC_APPLY_INTEGER(pCodecPar->profile, pInfo->nProfile); + XCODEC_APPLY_INTEGER(pCodecPar->width, pInfo->nWidth); + XCODEC_APPLY_INTEGER(pCodecPar->height, pInfo->nHeight); + + if (pInfo->codecId != AV_CODEC_ID_NONE) + pCodecPar->codec_id = pInfo->codecId; + + if (pInfo->pixFmt != AV_PIX_FMT_NONE) + pCodecPar->format = pInfo->pixFmt; + + return XSTDOK; +} + +XSTATUS XCodec_ApplyAudioParam(xcodec_t *pInfo, AVCodecParameters *pCodecPar) +{ + XASSERT((pCodecPar && pInfo), XSTDINV); + pCodecPar->codec_type = AVMEDIA_TYPE_AUDIO; + + XCODEC_APPLY_INTEGER(pCodecPar->bits_per_coded_sample, pInfo->nBitsPerSample); + XCODEC_APPLY_INTEGER(pCodecPar->sample_rate, pInfo->nSampleRate); + XCODEC_APPLY_INTEGER(pCodecPar->frame_size, pInfo->nFrameSize); + XCODEC_APPLY_INTEGER(pCodecPar->bit_rate, pInfo->nBitRate); + XCODEC_APPLY_INTEGER(pCodecPar->profile, pInfo->nProfile); + + if (pInfo->sampleFmt != AV_SAMPLE_FMT_NONE) + pCodecPar->format = pInfo->sampleFmt; + + if (pInfo->codecId != AV_CODEC_ID_NONE) + pCodecPar->codec_id = pInfo->codecId; + +#ifdef XCODEC_USE_NEW_CHANNEL + if (pInfo->channelLayout.nb_channels > 0) + av_channel_layout_copy(&pCodecPar->ch_layout, &pInfo->channelLayout); +#else + XCODEC_APPLY_INTEGER(pCodecPar->channel_layout, pInfo->nChannelLayout); + XCODEC_APPLY_INTEGER(pCodecPar->channels, pInfo->nChannels); +#endif + + return XSTDOK; +} + +XSTATUS XCodec_AddExtra(xcodec_t *pInfo, uint8_t *pExtraData, int nSize) +{ + XASSERT((pInfo != NULL), XSTDINV); + XCodec_Clear(pInfo); + + XASSERT_RET((pExtraData && nSize > 0), XSTDNON); + int nPaddingSize = AV_INPUT_BUFFER_PADDING_SIZE; + + pInfo->pExtraData = av_mallocz(nSize + nPaddingSize); + XASSERT((pInfo->pExtraData != NULL), XSTDERR); + + memcpy(pInfo->pExtraData, pExtraData, nSize); + pInfo->nExtraSize = nSize; + + return XSTDOK; +} + +XSTATUS XCodec_GetStreamExtra(xcodec_t *pInfo, AVStream *pStream) +{ + XASSERT((pInfo && pStream && pStream->codecpar), XSTDINV); + uint8_t *pExtraData = pStream->codecpar->extradata; + int nExtraSize = pStream->codecpar->extradata_size; + return XCodec_AddExtra(pInfo, pExtraData, nExtraSize); +} + +XSTATUS XCodec_ApplyStreamExtra(xcodec_t *pInfo, AVStream *pStream) +{ + XASSERT((pInfo != NULL && pStream != NULL), XSTDINV); + XASSERT((pInfo->pExtraData && pInfo->nExtraSize > 0), XSTDNON); + + AVCodecParameters *pCodecPar = pStream->codecpar; + int nPaddingSize = AV_INPUT_BUFFER_PADDING_SIZE; + + /* Store extra data in AVStream codec parameters */ + pCodecPar->extradata = av_mallocz(pInfo->nExtraSize + nPaddingSize); + memcpy(pCodecPar->extradata, pInfo->pExtraData, pInfo->nExtraSize); + pCodecPar->extradata_size = pInfo->nExtraSize; + + return XSTDOK; +} + +XSTATUS XCodec_ApplyToAVStream(xcodec_t *pInfo, AVStream *pStream) +{ + XASSERT((pInfo && pStream), XSTDINV); + AVCodecParameters *pCodecPar = pStream->codecpar; + pStream->time_base = pInfo->timeBase; + + if (pInfo->mediaType == AVMEDIA_TYPE_VIDEO) + XCodec_ApplyVideoParam(pInfo, pCodecPar); + else if (pInfo->mediaType == AVMEDIA_TYPE_AUDIO) + XCodec_ApplyAudioParam(pInfo, pCodecPar); + + if (pInfo->pExtraData && + pInfo->nExtraSize > 0) + XCodec_ApplyStreamExtra(pInfo, pStream); + + return XSTDOK; +} + +enum AVMediaType XCodec_GetMediaType(const char *pMediaTypeStr) +{ + if (!strncmp(pMediaTypeStr, "audio", 5)) + return AVMEDIA_TYPE_AUDIO; + else if (!strncmp(pMediaTypeStr, "video", 5)) + return AVMEDIA_TYPE_VIDEO; + else if (!strncmp(pMediaTypeStr, "subtitle", 8)) + return AVMEDIA_TYPE_SUBTITLE; + else if (!strncmp(pMediaTypeStr, "data", 4)) + return AVMEDIA_TYPE_DATA; + else if (!strncmp(pMediaTypeStr, "attachment", 10)) + return AVMEDIA_TYPE_ATTACHMENT; + + return AVMEDIA_TYPE_UNKNOWN; +} + +enum AVCodecID XCodec_GetIDByName(const char *pCodecName) +{ + const AVCodecDescriptor *pDesc = avcodec_descriptor_get_by_name(pCodecName); + XASSERT(pDesc, AV_CODEC_ID_NONE); + return pDesc->id; +} + +char* XCodec_GetNameByID(char *pName, size_t nLength, enum AVCodecID codecId) +{ + const AVCodecDescriptor *pDesc = avcodec_descriptor_get(codecId); + if (pDesc == NULL) xstrncpy(pName, nLength, "none"); + else xstrncpy(pName, nLength, pDesc->name); + return pName; +} + +char* XCodec_GetScaleFmtStr(char *pOutput, size_t nLength, xscale_fmt_t eScaleFmt) +{ + XASSERT_RET((pOutput && nLength), NULL); + if (eScaleFmt == XSCALE_FMT_STRETCH) xstrncpy(pOutput, nLength, "stretch"); + else if (eScaleFmt == XSCALE_FMT_ASPECT) xstrncpy(pOutput, nLength, "aspect"); + else xstrncpy(pOutput, nLength, "none");; + return pOutput; +} + +xscale_fmt_t XCodec_GetScaleFmt(const char *pScaleFmt) +{ + XASSERT_RET(pScaleFmt, XSCALE_FMT_NONE); + if (!strncmp(pScaleFmt, "stretch", 7)) return XSCALE_FMT_STRETCH; + else if (!strncmp(pScaleFmt, "aspect", 6)) return XSCALE_FMT_ASPECT; + return XSCALE_FMT_NONE; +} + +size_t XCodec_DumpStr(xcodec_t *pCodec, char *pOutput, size_t nSize) +{ + XASSERT((pCodec && pOutput && nSize), XSTDINV); + char sMediaSpecific[XSTR_TINY]; + char sCodecIdStr[XSTR_TINY]; + + sMediaSpecific[0] = '\0'; + sCodecIdStr[0] = '\0'; + + XCodec_GetNameByID(sCodecIdStr, sizeof(sCodecIdStr), pCodec->codecId); + const char *pMediaType = av_get_media_type_string(pCodec->mediaType); + + if (pCodec->mediaType == AVMEDIA_TYPE_AUDIO) + { + const char *pFormat = av_get_sample_fmt_name(pCodec->sampleFmt); + const char *pSampleFormat = pFormat != NULL ? pFormat : "unknown"; + + xstrncpyf(sMediaSpecific, sizeof(sMediaSpecific), + "fmt(%s), chan(%d), sr(%d), bps(%d)", + pSampleFormat, pCodec->nChannels, + pCodec->nSampleRate, pCodec->nBitsPerSample); + } + else if (pCodec->mediaType == AVMEDIA_TYPE_VIDEO) + { + const char *pFormat = av_get_pix_fmt_name(pCodec->pixFmt); + const char *pPixFmt = pFormat != NULL ? pFormat : "unknown"; + + xstrncpyf(sMediaSpecific, sizeof(sMediaSpecific), + "fmt(%s), size(%dx%d), ar(%d:%d), fr(%d.%d)", + pPixFmt, pCodec->nWidth, pCodec->nHeight, + pCodec->aspectRatio.num, pCodec->aspectRatio.den, + pCodec->frameRate.num, pCodec->frameRate.den); + } + + return xstrncpyf(pOutput, nSize, + "type(%s), codec(%s), %s, tb(%d.%d)", + pMediaType, sCodecIdStr, sMediaSpecific, + pCodec->timeBase.num, pCodec->timeBase.den); +} + +size_t XCodec_DumpJSON(xcodec_t *pCodec, char *pOutput, size_t nSize, size_t nTabSize, xbool_t bPretty) +{ + XASSERT((pCodec && pOutput && nSize), XSTDINV); + xjson_obj_t *pCodecObj = XJSON_NewObject(NULL, XFALSE); + XASSERT((pCodecObj != NULL), XSTDERR); + + const char *pMediaType = av_get_media_type_string(pCodec->mediaType); + const char *pType = pMediaType != NULL ? pMediaType : "unknown"; + + XJSON_AddObject(pCodecObj, XJSON_NewString("mediaType", pType)); + + char sCodecIdStr[XSTR_TINY]; + XCodec_GetNameByID(sCodecIdStr, sizeof(sCodecIdStr), pCodec->codecId); + XJSON_AddObject(pCodecObj, XJSON_NewString("codecId", sCodecIdStr)); + + xjson_obj_t *pRationalObj = XJSON_NewArray("timeBase", XFALSE); + if (pRationalObj != NULL) + { + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->timeBase.num)); + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->timeBase.den)); + + pRationalObj->nAllowLinter = XFALSE; + XJSON_AddObject(pCodecObj, pRationalObj); + } + + XJSON_AddObject(pCodecObj, XJSON_NewInt("compressLevel", pCodec->nCompressLevel)); + XJSON_AddObject(pCodecObj, XJSON_NewInt("frameSize", pCodec->nFrameSize)); + XJSON_AddObject(pCodecObj, XJSON_NewInt("bitRate", pCodec->nBitRate)); + XJSON_AddObject(pCodecObj, XJSON_NewInt("profile", pCodec->nProfile)); + + if (pCodec->mediaType == AVMEDIA_TYPE_AUDIO) + { + const char *pSampleFormat = av_get_sample_fmt_name(pCodec->sampleFmt); + const char *pFormat = pSampleFormat != NULL ? pSampleFormat : "unknown"; + + XJSON_AddObject(pCodecObj, XJSON_NewString("sampleFmt", pFormat)); + XJSON_AddObject(pCodecObj, XJSON_NewInt("bitsPerSample", pCodec->nBitsPerSample)); + XJSON_AddObject(pCodecObj, XJSON_NewInt("sampleRate", pCodec->nSampleRate)); + XJSON_AddObject(pCodecObj, XJSON_NewInt("channels", pCodec->nChannels)); + } + else if (pCodec->mediaType == AVMEDIA_TYPE_VIDEO) + { + char sScaleFmt[XSTR_TINY]; + XCodec_GetScaleFmtStr(sScaleFmt, sizeof(sScaleFmt), pCodec->scaleFmt); + + const char *pPixelFormat = av_get_pix_fmt_name(pCodec->pixFmt); + const char *pFormat = pPixelFormat != NULL ? pPixelFormat : "unknown"; + + XJSON_AddObject(pCodecObj, XJSON_NewString("scaleFmt", sScaleFmt)); + XJSON_AddObject(pCodecObj, XJSON_NewString("pixFmt", pFormat)); + + pRationalObj = XJSON_NewArray("aspectRatio", XFALSE); + if (pRationalObj != NULL) + { + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->aspectRatio.num)); + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->aspectRatio.den)); + + pRationalObj->nAllowLinter = XFALSE; + XJSON_AddObject(pCodecObj, pRationalObj); + } + + pRationalObj = XJSON_NewArray("frameRate", XFALSE); + if (pRationalObj != NULL) + { + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->frameRate.num)); + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->frameRate.den)); + + pRationalObj->nAllowLinter = XFALSE; + XJSON_AddObject(pCodecObj, pRationalObj); + } + + pRationalObj = XJSON_NewArray("size", XFALSE); + if (pRationalObj != NULL) + { + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->nWidth)); + XJSON_AddObject(pRationalObj, XJSON_NewInt(NULL, pCodec->nHeight)); + + pRationalObj->nAllowLinter = XFALSE; + XJSON_AddObject(pCodecObj, pRationalObj); + } + } + + xjson_writer_t writer; + XJSON_InitWriter(&writer, pOutput, nSize); + + writer.nTabSize = nTabSize; + writer.nPretty = bPretty; + + XJSON_WriteObject(pCodecObj, &writer); + XJSON_DestroyWriter(&writer); + XJSON_FreeObject(pCodecObj); + + return writer.nLength; +} + +XSTATUS XCodec_FromJSON(xcodec_t *pCodec, char* pJson, size_t nLength) +{ + XASSERT((pCodec && pJson && nLength), XSTDINV); + XCodec_Init(pCodec); + + xjson_t json; + if (!XJSON_Parse(&json, pJson, nLength)) + { + XJSON_Destroy(&json); + return XSTDERR; + } + + xjson_obj_t *pRootObj = json.pRootObj; + xjson_obj_t *pRationalObj = NULL; + xjson_obj_t *pChildObj = NULL; + + const char *pMediaType = XJSON_GetString(XJSON_GetObject(pRootObj, "mediaType")); + if (xstrused(pMediaType)) pCodec->mediaType = XCodec_GetMediaType(pMediaType); + + const char *pCodecId = XJSON_GetString(XJSON_GetObject(pRootObj, "codecId")); + if (xstrused(pCodecId)) pCodec->codecId = XCodec_GetIDByName(pCodecId); + + pRationalObj = XJSON_GetObject(pRootObj, "timeBase"); + if (pRationalObj != NULL && XJSON_GetArrayLength(pRationalObj) >= 2) + { + pChildObj = XJSON_GetArrayItem(pRationalObj, 0); + if (pChildObj != NULL) pCodec->timeBase.num = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetArrayItem(pRationalObj, 1); + if (pChildObj != NULL) pCodec->timeBase.den = XJSON_GetInt(pChildObj); + } + + pChildObj = XJSON_GetObject(pRootObj, "bitRate"); + if (pChildObj != NULL) pCodec->nBitRate = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetObject(pRootObj, "compressLevel"); + if (pChildObj != NULL) pCodec->nCompressLevel = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetObject(pRootObj, "frameSize"); + if (pChildObj != NULL) pCodec->nFrameSize = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetObject(pRootObj, "profile"); + if (pChildObj != NULL) pCodec->nProfile = XJSON_GetInt(pChildObj); + + if (pCodec->mediaType == AVMEDIA_TYPE_AUDIO) + { + const char *pSampleFmt = XJSON_GetString(XJSON_GetObject(pRootObj, "sampleFmt")); + if (xstrused(pSampleFmt)) pCodec->sampleFmt = av_get_sample_fmt(pSampleFmt); + + pChildObj = XJSON_GetObject(pRootObj, "bitsPerSample"); + if (pChildObj != NULL) pCodec->nBitsPerSample = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetObject(pRootObj, "sampleRate"); + if (pChildObj != NULL) pCodec->nSampleRate = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetObject(pRootObj, "channels"); + if (pChildObj != NULL) + { + int nChannels = XJSON_GetInt(pChildObj); + XCodec_InitChannels(pCodec, nChannels); + } + } + else if (pCodec->mediaType == AVMEDIA_TYPE_VIDEO) + { + const char *pPixFmt = XJSON_GetString(XJSON_GetObject(pRootObj, "pixFmt")); + if (xstrused(pPixFmt)) pCodec->pixFmt = av_get_pix_fmt(pPixFmt); + + const char *pScaleFmt = XJSON_GetString(XJSON_GetObject(pRootObj, "scaleFmt")); + if (xstrused(pScaleFmt)) pCodec->scaleFmt = XCodec_GetScaleFmt(pScaleFmt); + + pRationalObj = XJSON_GetObject(pRootObj, "aspectRatio"); + if (pRationalObj != NULL && XJSON_GetArrayLength(pRationalObj) >= 2) + { + pChildObj = XJSON_GetArrayItem(pRationalObj, 0); + if (pChildObj != NULL) pCodec->aspectRatio.num = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetArrayItem(pRationalObj, 1); + if (pChildObj != NULL) pCodec->aspectRatio.den = XJSON_GetInt(pChildObj); + } + + pRationalObj = XJSON_GetObject(pRootObj, "frameRate"); + if (pRationalObj != NULL && XJSON_GetArrayLength(pRationalObj) >= 2) + { + pChildObj = XJSON_GetArrayItem(pRationalObj, 0); + if (pChildObj != NULL) pCodec->frameRate.num = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetArrayItem(pRationalObj, 1); + if (pChildObj != NULL) pCodec->frameRate.den = XJSON_GetInt(pChildObj); + } + + pRationalObj = XJSON_GetObject(pRootObj, "size"); + if (pRationalObj != NULL && XJSON_GetArrayLength(pRationalObj) >= 2) + { + pChildObj = XJSON_GetArrayItem(pRationalObj, 0); + if (pChildObj != NULL) pCodec->nWidth = XJSON_GetInt(pChildObj); + + pChildObj = XJSON_GetArrayItem(pRationalObj, 1); + if (pChildObj != NULL) pCodec->nHeight = XJSON_GetInt(pChildObj); + } + } + + XJSON_Destroy(&json); + return XSTDOK; +} \ No newline at end of file diff --git a/src/codec.h b/src/codec.h new file mode 100644 index 0000000..c5d4c42 --- /dev/null +++ b/src/codec.h @@ -0,0 +1,114 @@ +/*! + * @file libxmedia/src/codec.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio codec + * functionality based on the FFMPEG API and libraries. + */ + +#ifndef __XMEDIA_CODEC_H__ +#define __XMEDIA_CODEC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "frame.h" + +#define XCODEC_NOT_SET XSTDINV +#define XRATIONAL_NOT_SET (AVRational){XCODEC_NOT_SET, XCODEC_NOT_SET} + +typedef struct xcodec_ { + enum AVMediaType mediaType; + enum AVCodecID codecId; + AVRational timeBase; + int64_t nBitRate; + int nFrameSize; + int nProfile; + int nCompressLevel; + + /* Video codec properties */ + enum AVPixelFormat pixFmt; + xscale_fmt_t scaleFmt; + AVRational aspectRatio; + AVRational frameRate; + int nWidth; + int nHeight; + + /* Audio codec properties */ + enum AVSampleFormat sampleFmt; + int nSampleRate; + int nChannels; + int nBitsPerSample; + +#ifdef XCODEC_USE_NEW_CHANNEL + AVChannelLayout channelLayout; +#else + uint64_t nChannelLayout; +#endif + + /* Codec context extra data */ + uint8_t* pExtraData; + int nExtraSize; +} xcodec_t; + +typedef struct x264_extra_ { + /* h264 extradata */ + int nSPSSize; + int nPPSSize; + uint8_t* pSPS; + uint8_t* pPPS; +} x264_extra_t; + +typedef struct xopus_header_ { + uint8_t nChannels; + uint16_t nPreSkip; + uint32_t nInputSampleRate; + int16_t nOutputGain; + uint8_t nChannelMappingFamily; +} xopus_header_t; + +xscale_fmt_t XCodec_GetScaleFmt(const char *pScaleFmt); +char* XCodec_GetScaleFmtStr(char *pOutput, size_t nLength, xscale_fmt_t eScaleFmt); + +enum AVCodecID XCodec_GetIDByName(const char *pCodecName); +char* XCodec_GetNameByID(char *pName, size_t nLength, enum AVCodecID codecId); + +size_t XCodec_DumpJSON(xcodec_t *pCodec, char *pOutput, size_t nSize, size_t nTabSize, xbool_t bPretty); +size_t XCodec_DumpStr(xcodec_t *pCodec, char *pOutput, size_t nSize); +XSTATUS XCodec_FromJSON(xcodec_t *pCodec, char* pJson, size_t nLength); + +uint8_t* X264_CreateExtra(x264_extra_t *pExtra, int *pExtraSize); +uint8_t* XOPUS_CreateExtra(xopus_header_t *pHeader, int *pExtraSize); + +void XCodec_Init(xcodec_t *pInfo); +void XCodec_Clear(xcodec_t *pInfo); +XSTATUS XCodec_Copy(xcodec_t *pDst, const xcodec_t *pSrc); + +void XCodec_InitChannels(xcodec_t *pInfo, int nChannels); +void XCodec_CopyChannels(xcodec_t *pDst, const xcodec_t *pSrc); + +XSTATUS XCodec_ApplyVideoParam(xcodec_t *pInfo, AVCodecParameters *pCodecPar); +XSTATUS XCodec_ApplyAudioParam(xcodec_t *pInfo, AVCodecParameters *pCodecPar); + +XSTATUS XCodec_ApplyVideoCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx); +XSTATUS XCodec_ApplyAudioCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx); + +XSTATUS XCodec_AddExtra(xcodec_t *pInfo, uint8_t *pExtraData, int nSize); +XSTATUS XCodec_GetStreamExtra(xcodec_t *pInfo, AVStream *pStream); +XSTATUS XCodec_ApplyStreamExtra(xcodec_t *pInfo, AVStream *pStream); + +XSTATUS XCodec_GetFromAVStream(xcodec_t *pInfo, AVStream *pStream); +XSTATUS XCodec_ApplyToAVStream(xcodec_t *pInfo, AVStream *pStream); + +XSTATUS XCodec_GetFromAVCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx); +XSTATUS XCodec_ApplyToAVCodec(xcodec_t *pInfo, AVCodecContext *pCodecCtx); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_CODEC_H__ */ diff --git a/src/decoder.c b/src/decoder.c new file mode 100644 index 0000000..4c8fcf6 --- /dev/null +++ b/src/decoder.c @@ -0,0 +1,277 @@ +/*! + * @file libxmedia/src/decoder.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio decoding + * functionality based on the FFMPEG API and libraries. + */ + +#include "decoder.h" + +void XDecoder_Init(xdecoder_t *pDecoder) +{ + XASSERT_VOID(pDecoder); + XStreams_Init(&pDecoder->streams); + XStat_Init(&pDecoder->status, XSTDNON, NULL, NULL); + + pDecoder->pDemuxOpts = NULL; + pDecoder->bDemuxOnly = XFALSE; + pDecoder->bHaveInput = XFALSE; + + pDecoder->statusCallback = NULL; + pDecoder->errorCallback = NULL; + pDecoder->frameCallback = NULL; + pDecoder->pUserCtx = NULL; + pDecoder->pFmtCtx = NULL; +} + +void XDecoder_Destroy(xdecoder_t *pDecoder) +{ + XASSERT_VOID(pDecoder); + XStreams_Destroy(&pDecoder->streams); + + if (pDecoder->pDemuxOpts != NULL) + { + av_dict_free(&pDecoder->pDemuxOpts); + pDecoder->pDemuxOpts = NULL; + } + + if (pDecoder->pFmtCtx != NULL) + { + if (pDecoder->bHaveInput) avformat_close_input(&pDecoder->pFmtCtx); + else avformat_free_context(pDecoder->pFmtCtx); + + pDecoder->bHaveInput = XFALSE; + pDecoder->pFmtCtx = NULL; + } +} + +XSTATUS XDecoder_OpenCodec(xdecoder_t *pDecoder, xcodec_t *pCodec) +{ + XASSERT(pDecoder, XSTDINV); + xstatus_t *pStatus = &pDecoder->status; + + XASSERT(pCodec, XStat_ErrCb(pStatus, "Invalid codec argument")); + const AVCodec *pAvCodec = avcodec_find_decoder(pCodec->codecId); + XASSERT(pAvCodec, XStat_ErrCb(pStatus, "Codec is not found: %d", (int)pCodec->codecId)); + + /* Create unique stream index */ + int nStreamIndex = (int)XArray_Used(&pDecoder->streams); + while (XStreams_GetBySrcIndex(&pDecoder->streams, nStreamIndex)) nStreamIndex++; + + xstream_t *pStream = XStreams_NewStream(&pDecoder->streams); + XASSERT(pStream, XStat_ErrCb(pStatus, "Failed to create new stream: src(%d)", nStreamIndex)); + + pStream->pCodecCtx = avcodec_alloc_context3(pAvCodec); + XASSERT(pStream->pCodecCtx, XStat_ErrCb(pStatus, "Failed to alloc decoder context: src(%d)", nStreamIndex)); + + XSTATUS nStatus = XCodec_ApplyToAVCodec(pCodec, pStream->pCodecCtx); + XASSERT((nStatus == XSTDOK), XStat_ErrCb(pStatus, "Failed to apply codec to context: src(%d)", nStreamIndex)); + + pStatus->nAVStatus = avcodec_open2(pStream->pCodecCtx, pAvCodec, NULL); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to open decoder: src(%d)", nStreamIndex)); + + XCodec_GetFromAVCodec(&pStream->codecInfo, pStream->pCodecCtx); + pStream->bCodecOpen = XTRUE; + + char sCodecId[XSTR_TINY]; + XCodec_GetNameByID(sCodecId, sizeof(sCodecId), pStream->codecInfo.codecId); + + XStat_InfoCb(pStatus, "Decoding codec: type(%d), tb(%d.%d), name(%s), src(%d)", + (int)pStream->codecInfo.mediaType, pStream->pCodecCtx->time_base.num, + pStream->pCodecCtx->time_base.den, sCodecId, nStreamIndex); + + pStream->nSrcIndex = nStreamIndex; + pStream->bCodecOpen = XTRUE; + return nStreamIndex; +} + +XSTATUS XDecoder_OpenInput(xdecoder_t *pDecoder, const char *pInput, const char *pInputFmt) +{ + XASSERT(pDecoder, XSTDINV); + xstatus_t *pStatus = &pDecoder->status; + XASSERT(pInput, XStat_ErrCb(pStatus, "Invalid input argument")); + +#ifdef XCODEC_USE_NEW_FIFO + const AVInputFormat *pAvInFmt = pInputFmt ? av_find_input_format(pInputFmt) : NULL; +#else + AVInputFormat *pAvInFmt = pInputFmt ? av_find_input_format(pInputFmt) : NULL; +#endif + + pStatus->nAVStatus = avformat_open_input(&pDecoder->pFmtCtx, pInput, pAvInFmt, &pDecoder->pDemuxOpts); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Cannot open input: %s", pInput)); + + pStatus->nAVStatus = avformat_find_stream_info(pDecoder->pFmtCtx, NULL); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Cannot find stream info: %s", pInput)); + + pDecoder->bHaveInput = XTRUE; + unsigned int i; + + for (i = 0; i < pDecoder->pFmtCtx->nb_streams; i++) + { + xstream_t *pStream = XStreams_GetBySrcIndex(&pDecoder->streams, i); + XASSERT((pStream == NULL), XStat_ErrCb(pStatus, "Stream already exists: src(%u)", i)); + + char sCodecIdStr[XSTR_TINY]; + AVStream *pAvStream = pDecoder->pFmtCtx->streams[i]; + enum AVCodecID codecId = pAvStream->codecpar->codec_id; + XCodec_GetNameByID(sCodecIdStr, sizeof(sCodecIdStr), codecId); + + if (pAvStream->codecpar->codec_type != AVMEDIA_TYPE_VIDEO && + pAvStream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) + { + XStat_InfoCb(pStatus, "Skipping stream: type(%s), codec(%s), src(%d)", + av_get_media_type_string(pAvStream->codecpar->codec_type), sCodecIdStr, i); + + pAvStream->discard = AVDISCARD_ALL; + continue; + } + + pStream = XStreams_NewStream(&pDecoder->streams); + XASSERT(pStream, XStat_ErrCb(pStatus, "Failed to create new stream: src(%u)", i)); + + char sCodecStr[XSTR_MID]; + XCodec_GetFromAVStream(&pStream->codecInfo, pAvStream); + XCodec_DumpStr(&pStream->codecInfo, sCodecStr, sizeof(sCodecStr)); + + pStream->nSrcIndex = pAvStream->index; + pStream->pAvStream = pAvStream; + + if (pDecoder->bDemuxOnly) + { + XStat_InfoCb(pStatus, "Demuxing stream: %s, src(%d)", sCodecStr, i); + return XSTDOK; + } + + const AVCodec *pDecCodec = avcodec_find_decoder(pAvStream->codecpar->codec_id); + XASSERT(pDecCodec, XStat_ErrCb(pStatus, "Failed to find decoder: id(%d), src(%u)", + (int)pAvStream->codecpar->codec_id, i)); + + pStream->pCodecCtx = avcodec_alloc_context3(pDecCodec); + XASSERT(pStream->pCodecCtx, XStat_ErrCb(pStatus, "Failed to alloc decoder context: src(%u)", i)); + + pStatus->nAVStatus = avcodec_parameters_to_context(pStream->pCodecCtx, pAvStream->codecpar); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to copy codec parameters: src(%u)", i)); + + if (pStream->pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO) + pStream->pCodecCtx->framerate = av_guess_frame_rate(pDecoder->pFmtCtx, pAvStream, NULL); + + if (pStream->pCodecCtx->codec_type == AVMEDIA_TYPE_VIDEO || + pStream->pCodecCtx->codec_type == AVMEDIA_TYPE_AUDIO) + { + pStatus->nAVStatus = avcodec_open2(pStream->pCodecCtx, pDecCodec, NULL); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to open decoder: src(%u)", i)); + pStream->bCodecOpen = XTRUE; + } + + XStat_InfoCb(pStatus, "Decoding stream: %s, src(%d)", sCodecStr, i); + } + + return XSTDOK; +} + +XSTATUS XDecoder_Seek(xdecoder_t *pDecoder, int nStream, int64_t nTS, int nFlags) +{ + XASSERT(pDecoder, XSTDINV); + xstatus_t *pStatus = &pDecoder->status; + + XASSERT(pDecoder->bHaveInput, XStat_ErrCb(pStatus, "Input format is not open")); + pStatus->nAVStatus = av_seek_frame(pDecoder->pFmtCtx, nStream, nTS, nFlags); + return pStatus->nAVStatus; +} + +XSTATUS XDecoder_ReadPacket(xdecoder_t *pDecoder, AVPacket *pPacket) +{ + XASSERT(pDecoder, XSTDINV); + xstatus_t *pStatus = &pDecoder->status; + + XASSERT(pPacket, XStat_ErrCb(pStatus, "Invalid packet argument")); + XASSERT(pDecoder->bHaveInput, XStat_ErrCb(pStatus, "Input format is not open")); + + pStatus->nAVStatus = av_read_frame(pDecoder->pFmtCtx, pPacket); + return pStatus->nAVStatus; +} + +XSTATUS XDecoder_DecodePacket(xdecoder_t *pDecoder, AVPacket *pPacket) +{ + XASSERT(pDecoder, XSTDINV); + xstatus_t *pStatus = &pDecoder->status; + + XASSERT(pPacket, XStat_ErrCb(pStatus, "Invalid packet argument")); + XASSERT(pDecoder->frameCallback, XStat_ErrCb(pStatus, "Decoder frame callback is not set")); + + xstream_t *pStream = XStreams_GetBySrcIndex(&pDecoder->streams, pPacket->stream_index); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: src(%d)", pPacket->stream_index)); + XASSERT(pStream->bCodecOpen, XStat_ErrCb(pStatus, "Codec is not open: src(%d)", pStream->nSrcIndex)); + + AVFrame *pFrame = XStream_GetOrCreateFrame(pStream); + XASSERT(pFrame, XStat_ErrCb(pStatus, "Failed to alloc frame: src(%d)", pStream->nSrcIndex)); + + pFrame->pts = pPacket->pts; + pFrame->pkt_dts = pPacket->dts; + + pStatus->nAVStatus = avcodec_send_packet(pStream->pCodecCtx, pPacket); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, + "Failed to decode packet: src(%d)", pStream->nSrcIndex)); + + while (pStatus->nAVStatus >= 0) + { + pStatus->nAVStatus = avcodec_receive_frame(pStream->pCodecCtx, pFrame); + if (pStatus->nAVStatus == AVERROR(EAGAIN) || + pStatus->nAVStatus == AVERROR_EOF) return XSTDOK; + + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, + "Failed to receive frame: src(%d)", pStream->nSrcIndex)); + + pStatus->nAVStatus = pDecoder->frameCallback(pDecoder->pUserCtx, pFrame, pStream->nSrcIndex); + av_frame_unref(pFrame); + } + + return pStatus->nAVStatus; +} + +AVPacket* XDecoder_CreatePacket(xdecoder_t *pDecoder, uint8_t *pData, size_t nSize) +{ + XASSERT(pDecoder, NULL); + xstatus_t *pStatus = &pDecoder->status; + + XASSERT(pData, XStat_ErrPtr(pStatus, "Invalid data argument")); + XASSERT(nSize, XStat_ErrPtr(pStatus, "Invalid size argument")); + + AVPacket *pPacket = av_packet_alloc(); + XASSERT(pPacket, XStat_ErrPtr(pStatus, "Failed to allocate AVPacket")); + + pPacket->data = pData; + pPacket->size = (int)nSize; + pPacket->stream_index = 0; + + return pPacket; +} + +const xcodec_t* XDecoder_GetCodecInfo(xdecoder_t* pDecoder, int nStream) +{ + XASSERT(pDecoder, NULL); + xstatus_t *pStatus = &pDecoder->status; + + xstream_t *pStream = XStreams_GetBySrcIndex(&pDecoder->streams, nStream); + if (pStream == NULL) + { + XStat_ErrCb(pStatus, "Stream is not found: src(%d)", nStream); + return NULL; + } + + return XStream_GetCodecInfo(pStream); +} + +XSTATUS XDecoder_CopyCodecInfo(xdecoder_t* pDecoder, xcodec_t *pCodecInfo, int nStream) +{ + XASSERT(pDecoder, XSTDINV); + xstatus_t *pStatus = &pDecoder->status; + XASSERT(pCodecInfo, XStat_ErrCb(pStatus, "Invalid codec info argument")); + + xstream_t *pStream = XStreams_GetBySrcIndex(&pDecoder->streams, nStream); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: src(%d)", nStream)); + return XStream_CopyCodecInfo(pStream, pCodecInfo); +} \ No newline at end of file diff --git a/src/decoder.h b/src/decoder.h new file mode 100644 index 0000000..07635bd --- /dev/null +++ b/src/decoder.h @@ -0,0 +1,62 @@ +/*! + * @file libxmedia/src/decoder.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio decoding + * functionality based on the FFMPEG API and libraries. + */ + +#ifndef __XMEDIA_DECODER_H__ +#define __XMEDIA_DECODER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "stream.h" +#include "status.h" + +typedef int(*xdecoder_pkt_cb_t)(void *pUserCtx, AVFrame *pFrame, int nStreamIndex); +typedef void(*xdecoder_stat_cb_t)(void *pUserCtx, const char *pStatus); +typedef void(*xdecoder_err_cb_t)(void *pUserCtx, const char *pErrStr); + +typedef struct xdecoder_ { + /* Decoder/demuxer context */ + AVFormatContext* pFmtCtx; + AVDictionary* pDemuxOpts; + xarray_t streams; + + /* User input context */ + xbool_t bDemuxOnly; + xdecoder_stat_cb_t statusCallback; + xdecoder_err_cb_t errorCallback; + xdecoder_pkt_cb_t frameCallback; + void* pUserCtx; + + /* Status related context */ + xbool_t bHaveInput; + xstatus_t status; +} xdecoder_t; + +void XDecoder_Init(xdecoder_t *pDecoder); +void XDecoder_Destroy(xdecoder_t *pDecoder); + +XSTATUS XDecoder_OpenInput(xdecoder_t *pDecoder, const char *pInput, const char *pInputFmt); +XSTATUS XDecoder_OpenCodec(xdecoder_t *pDecoder, xcodec_t *pCodec); + +const xcodec_t* XDecoder_GetCodecInfo(xdecoder_t* pDecoder, int nStream); +XSTATUS XDecoder_CopyCodecInfo(xdecoder_t* pDecoder, xcodec_t *pCodecInfo, int nStream); +XSTATUS XDecoder_Seek(xdecoder_t *pDecoder, int nStream, int64_t nTS, int nFlags); + +AVPacket* XDecoder_CreatePacket(xdecoder_t *pDecoder, uint8_t *pData, size_t nSize); +XSTATUS XDecoder_ReadPacket(xdecoder_t *pDecoder, AVPacket *pPacket); +XSTATUS XDecoder_DecodePacket(xdecoder_t *pDecoder, AVPacket *pPacket); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_DECODER_H__ */ diff --git a/src/encoder.c b/src/encoder.c new file mode 100644 index 0000000..cafa80f --- /dev/null +++ b/src/encoder.c @@ -0,0 +1,774 @@ +/*! + * @file libxmedia/src/encoder.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio encoding + * functionality based on the FFMPEG API and libraries. + */ + +#include "encoder.h" + +void XEncoder_Init(xencoder_t *pEncoder) +{ + XASSERT_VOID(pEncoder); + XStreams_Init(&pEncoder->streams); + XStat_Init(&pEncoder->status, XSTDNON, NULL, NULL); + + pEncoder->sOutputPath[0] = XSTR_NUL; + pEncoder->sOutFormat[0] = XSTR_NUL; + pEncoder->nIOBuffSize = 0; + pEncoder->pIOBuffer = NULL; + pEncoder->pIOCtx = NULL; + pEncoder->pFmtCtx = NULL; + + pEncoder->statusCallback = NULL; + pEncoder->packetCallback = NULL; + pEncoder->muxerCallback = NULL; + pEncoder->errorCallback = NULL; + pEncoder->pUserCtx = NULL; + + pEncoder->nStartTime = XSTDNON; + pEncoder->eTSType = XPTS_RESCALE; + pEncoder->nTSFix = XSTDNON; + + pEncoder->bOutputOpen = XFALSE; + pEncoder->bMuxOnly = XFALSE; +} + +void XEncoder_Destroy(xencoder_t *pEncoder) +{ + XASSERT_VOID(pEncoder); + XStreams_Destroy(&pEncoder->streams); + pEncoder->bOutputOpen = XFALSE; + + if (pEncoder->pFmtCtx != NULL && pEncoder->pIOCtx == NULL && + !(pEncoder->pFmtCtx->oformat->flags & AVFMT_NOFILE)) + { + avio_closep(&pEncoder->pFmtCtx->pb); + pEncoder->pFmtCtx->pb = NULL; + } + + if (pEncoder->pIOCtx) + { + avio_context_free(&pEncoder->pIOCtx); + pEncoder->pIOCtx = NULL; + } + + if (pEncoder->pIOBuffer) + { + av_free(pEncoder->pIOBuffer); + pEncoder->pIOBuffer = NULL; + } + + if (pEncoder->pFmtCtx != NULL) + { + avformat_free_context(pEncoder->pFmtCtx); + pEncoder->pFmtCtx = NULL; + } +} + +XSTATUS XEncoder_OpenFormat(xencoder_t *pEncoder, const char *pFormat, const char *pOutputUrl) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + /* Validate arguments */ + XASSERT((pFormat || pOutputUrl), XStat_ErrCb(pStatus, "Invalid format arguments")); + if (pOutputUrl) xstrncpy(pEncoder->sOutputPath, sizeof(pEncoder->sOutputPath), pOutputUrl); + if (pFormat) xstrncpy(pEncoder->sOutFormat, sizeof(pEncoder->sOutFormat), pFormat); + + int nStatus = avformat_alloc_output_context2(&pEncoder->pFmtCtx, NULL, pFormat, pOutputUrl); + XASSERT((nStatus >= 0), XStat_ErrCb(pStatus, "Failed to alloc output context: fmt(%s) url(%s)", + pFormat != NULL ? pFormat : "NULL", pOutputUrl != NULL ? pOutputUrl : "NULL")); + + return XSTDOK; +} + +XSTATUS XEncoder_GuessFormat(xencoder_t *pEncoder, const char *pFormat, const char *pOutputUrl) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + /* Validate arguments */ + XASSERT((pFormat || pOutputUrl), XStat_ErrCb(pStatus, "Invalid format arguments")); + if (pOutputUrl) xstrncpy(pEncoder->sOutputPath, sizeof(pEncoder->sOutputPath), pOutputUrl); + if (pFormat) xstrncpy(pEncoder->sOutFormat, sizeof(pEncoder->sOutFormat), pFormat); + + /* Allocate format context */ + pEncoder->pFmtCtx = avformat_alloc_context(); + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Failed to alloc format context")); + + /* Guess output format for muxing by format name */ + pEncoder->pFmtCtx->oformat = av_guess_format(pFormat, pOutputUrl, NULL); + XASSERT(pEncoder->pFmtCtx->oformat, XStat_ErrCb(pStatus, "Format not found: fmt(%s) url(%s)", + pFormat != NULL ? pFormat : "NULL", pOutputUrl != NULL ? pOutputUrl : "NULL")); + + return XSTDOK; +} + +XSTATUS XEncoder_RestartCodec(xencoder_t *pEncoder, int nStreamIndex) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, nStreamIndex); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: %s", nStreamIndex)); + + if (pStream->pCodecCtx != NULL) + { + avcodec_free_context(&pStream->pCodecCtx); + pStream->pCodecCtx = NULL; + pStream->bCodecOpen = XFALSE; + } + + xcodec_t *pCodecInfo = &pStream->codecInfo; + const AVCodec *pAvCodec = avcodec_find_encoder(pCodecInfo->codecId); + XASSERT(pAvCodec, XStat_ErrCb(pStatus, "Failed to find encoder: %d", (int)pCodecInfo->codecId)); + + pStream->pCodecCtx = avcodec_alloc_context3(pAvCodec); + XASSERT(pStream->pCodecCtx, XStat_ErrCb(pStatus, "Failed to allocate encoder context")); + + XSTATUS nStatus = XCodec_ApplyToAVCodec(pCodecInfo, pStream->pCodecCtx); + XASSERT((nStatus == XSTDOK), XStat_ErrCb(pStatus, "Failed to apply codec to context: %d", pStream->nDstIndex)); + + if (pEncoder->pFmtCtx->oformat->flags & AVFMT_GLOBALHEADER) + pStream->pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + pStatus->nAVStatus = avcodec_open2(pStream->pCodecCtx, pAvCodec, NULL); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Cannot open encoder: %d", pStream->nDstIndex)); + + pStatus->nAVStatus = avcodec_parameters_from_context(pStream->pAvStream->codecpar, pStream->pCodecCtx); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to copy codec parameters: %d", pStream->nDstIndex)); + + XStat_InfoCb(pStatus, "Restarted codec: id(%d), type(%d), tb(%d.%d), ind(%d)", + (int)pCodecInfo->codecId, (int)pCodecInfo->mediaType, + pStream->pCodecCtx->time_base.num, + pStream->pCodecCtx->time_base.den, + pStream->nDstIndex); + + XCodec_GetFromAVCodec(&pStream->codecInfo, pStream->pCodecCtx); + pStream->bCodecOpen = XTRUE; + + return pStream->nDstIndex; +} + +XSTATUS XEncoder_OpenStream(xencoder_t *pEncoder, xcodec_t *pCodecInfo) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT((pCodecInfo != NULL), XStat_ErrCb(pStatus, "Invalid stream information argument")); + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Output format context is not initialized")); + + xstream_t *pStream = XStreams_NewStream(&pEncoder->streams); + XASSERT(pStream, XStat_ErrCb(pStatus, "Failed to create stream: %s", strerror(errno))); + + const AVCodec *pAvCodec = avcodec_find_encoder(pCodecInfo->codecId); + XASSERT(pAvCodec, XStat_ErrCb(pStatus, "Failed to find encoder: %d", (int)pCodecInfo->codecId)); + + pStream->pAvStream = avformat_new_stream(pEncoder->pFmtCtx, pAvCodec); + XASSERT(pStream->pAvStream, XStat_ErrCb(pStatus, "Failed to create avstream: %s", strerror(errno))); + + XCodec_Copy(&pStream->codecInfo, pCodecInfo); + int nDstIndex = pStream->pAvStream->index; + + XSTATUS nStatus = XCodec_ApplyToAVStream(pCodecInfo, pStream->pAvStream); + XASSERT((nStatus == XSTDOK), XStat_ErrCb(pStatus, "Failed to apply codec to stream: dst(%d)", nDstIndex)); + + char sCodecStr[XSTR_MID]; + sCodecStr[0] = '\0'; + + if (pEncoder->bMuxOnly) + { + XCodec_DumpStr(pCodecInfo, sCodecStr, sizeof(sCodecStr)); + XStat_InfoCb(pStatus, "Muxing stream: %s, dst(%d)", sCodecStr, nDstIndex); + + pStream->pAvStream->codecpar->codec_tag = 0; + pStream->nDstIndex = nDstIndex; + return pStream->nDstIndex; + } + + pStream->pCodecCtx = avcodec_alloc_context3(pAvCodec); + XASSERT(pStream->pCodecCtx, XStat_ErrCb(pStatus, "Failed to allocate encoder context")); + + nStatus = XCodec_ApplyToAVCodec(pCodecInfo, pStream->pCodecCtx); + XASSERT((nStatus == XSTDOK), XStat_ErrCb(pStatus, "Failed to apply codec to context: dst(%d)", nDstIndex)); + + if (pEncoder->pFmtCtx->oformat->flags & AVFMT_GLOBALHEADER) + pStream->pCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + + pStatus->nAVStatus = avcodec_open2(pStream->pCodecCtx, pAvCodec, NULL); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Cannot open encoder: dst(%d)", nDstIndex)); + + pStatus->nAVStatus = avcodec_parameters_from_context(pStream->pAvStream->codecpar, pStream->pCodecCtx); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to copy codec parameters: dst(%d)", nDstIndex)); + + XCodec_GetFromAVCodec(&pStream->codecInfo, pStream->pCodecCtx); + XCodec_DumpStr(pCodecInfo, sCodecStr, sizeof(sCodecStr)); + XStat_InfoCb(pStatus, "Encoding stream: %s, dst(%d)", sCodecStr, nDstIndex); + + pStream->nDstIndex = nDstIndex; + pStream->bCodecOpen = XTRUE; + + return pStream->nDstIndex; +} + +const xcodec_t* XEncoder_GetCodecInfo(xencoder_t* pEncoder, int nStreamIndex) +{ + XASSERT(pEncoder, NULL); + xstatus_t *pStatus = &pEncoder->status; + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, nStreamIndex); + if (pStream == NULL) + { + XStat_ErrCb(pStatus, "Stream is not found: dst(%d)", nStreamIndex); + return NULL; + } + + return XStream_GetCodecInfo(pStream); +} + +XSTATUS XEncoder_CopyCodecInfo(xencoder_t* pEncoder, xcodec_t *pCodecInfo, int nStreamIndex) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + XASSERT(pCodecInfo, XStat_ErrCb(pStatus, "Invalid codec info argument")); + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, nStreamIndex); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: dst(%d)", nStreamIndex)); + return XStream_CopyCodecInfo(pStream, pCodecInfo); +} + +XSTATUS XEncoder_WriteHeader(xencoder_t *pEncoder, AVDictionary *pHeaderOpts) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Invalid format context")); + XASSERT(pEncoder->bOutputOpen, XStat_ErrCb(pStatus, "Output context is not open")); + + pStatus->nAVStatus = avformat_write_header(pEncoder->pFmtCtx, &pHeaderOpts); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to write header")); + return XSTDOK; +} + +XSTATUS XEncoder_OpenOutput(xencoder_t *pEncoder, AVDictionary *pOpts) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Invalid format context")); + xbool_t bAvFormatNoFile = (pEncoder->pFmtCtx->oformat->flags & AVFMT_NOFILE); + + XASSERT((pEncoder->muxerCallback != NULL || xstrused(pEncoder->sOutputPath)), + XStat_ErrCb(pStatus, "Required muxer callback or output file to open the muxer")); + + if (pEncoder->muxerCallback) + { + if (!pEncoder->nIOBuffSize) pEncoder->nIOBuffSize = XENCODER_IO_SIZE; + size_t nPacketSize = pEncoder->nIOBuffSize; + + unsigned char *pBuffer = (unsigned char *)av_malloc(nPacketSize); + XASSERT(pBuffer, XStat_ErrCb(pStatus, "Failed to alloc output buffer: %s", strerror(errno))); + + pEncoder->pIOCtx = avio_alloc_context(pBuffer, nPacketSize, 1, + pEncoder->pUserCtx, NULL, pEncoder->muxerCallback, NULL); + + XASSERT_CALL(pEncoder->pIOCtx, av_free, pBuffer, + XStat_ErrCb(pStatus, "Failed to alloc output context")); + + pEncoder->pFmtCtx->packet_size = nPacketSize; + pEncoder->pFmtCtx->pb = pEncoder->pIOCtx; + pEncoder->pIOBuffer = pBuffer; + + XStat_InfoCb(pStatus, "Created output context: buffer(%zu)", nPacketSize); + } + else if (xstrused(pEncoder->sOutputPath)) + { + if (!bAvFormatNoFile) + { + pStatus->nAVStatus = avio_open(&pEncoder->pFmtCtx->pb, pEncoder->sOutputPath, AVIO_FLAG_WRITE); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to open output context")); + } + + XStat_InfoCb(pStatus, "Output context: url(%s), AVFMT_NOFILE(%s)", + pEncoder->sOutputPath, bAvFormatNoFile ? "true" : "false"); + } + + pEncoder->bOutputOpen = XTRUE; + return XEncoder_WriteHeader(pEncoder, pOpts); +} + +XSTATUS XEncoder_RescaleTS(xencoder_t *pEncoder, AVPacket *pPacket, xstream_t *pStream) +{ + XASSERT_RET(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + XASSERT(pPacket, XStat_ErrCb(pStatus, "Invalid packet argument")); + + if (pStream == NULL) + { + pStream = XStreams_GetByDstIndex(&pEncoder->streams, pPacket->stream_index); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: dst(%d)", pPacket->stream_index)); + } + + if (pEncoder->eTSType == XPTS_RESCALE) + { + /* Rescale packet timestamps and reset position */ + AVRational srcTimeBase = pStream->codecInfo.timeBase; + AVRational dstTimeBase = pStream->pAvStream->time_base; + + av_packet_rescale_ts(pPacket, srcTimeBase, dstTimeBase); + pPacket->pos = XSTDERR; /* Let FFMPEG decide position */ + } + else if (pEncoder->eTSType == XPTS_ROUND) + { + /* Rescale and round PTS/DTS to the nearest value */ + AVRational srcTimeBase = pStream->codecInfo.timeBase; + AVRational dstTimeBase = pStream->pAvStream->time_base; + enum AVRounding avRound = (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX); + + pPacket->pts = av_rescale_q_rnd(pPacket->pts, srcTimeBase, dstTimeBase, avRound); + pPacket->dts = av_rescale_q_rnd(pPacket->dts, srcTimeBase, dstTimeBase, avRound); + } + else if (pEncoder->eTSType == XPTS_CALCULATE) + { + /* Calculate PTS based on the elapsed time and clock rate */ + if (!pEncoder->nStartTime) pEncoder->nStartTime = XTime_GetStamp(); + AVRational dstTimeBase = pStream->pAvStream->time_base; + + uint64_t nCurrentTime = XTime_GetStamp(); + uint64_t nElapsedTime = nCurrentTime - pEncoder->nStartTime; + uint64_t nPTS = (nElapsedTime * dstTimeBase.den) / 1000000; + + pPacket->pts = nPTS; + pPacket->dts = nPTS; + } + else if (pEncoder->eTSType == XPTS_COMPUTE) + { + if (pStream->codecInfo.mediaType == AVMEDIA_TYPE_VIDEO) + { + /* Calculate the PTS and DTS based on the frame count and time base */ + AVRational srcTimeBase = av_inv_q(pStream->codecInfo.frameRate); + AVRational dstTimeBase = pStream->pAvStream->time_base; + + pPacket->duration = av_rescale_q(1, srcTimeBase, dstTimeBase); + pPacket->pts = av_rescale_q(pStream->nPacketCount, srcTimeBase, dstTimeBase); + pPacket->dts = pPacket->pts; + } + else if (pStream->codecInfo.mediaType == AVMEDIA_TYPE_AUDIO) + { + /* Calculate the PTS based on the sample rate and time base */ + AVRational srcTimeBase = (AVRational){1, pStream->codecInfo.nSampleRate}; + AVRational dstTimeBase = pStream->pAvStream->time_base; + int nSamplesPerFrame = pStream->codecInfo.nFrameSize; + + pPacket->duration = av_rescale_q(nSamplesPerFrame, srcTimeBase, dstTimeBase); + pPacket->pts = av_rescale_q(pStream->nPacketCount * nSamplesPerFrame, srcTimeBase, dstTimeBase); + pPacket->dts = pPacket->pts; + } + } + + return XSTDOK; +} + +XSTATUS XEncoder_FixTS(xencoder_t *pEncoder, AVPacket *pPacket, xstream_t *pStream) +{ + XASSERT_RET(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + XASSERT(pPacket, XStat_ErrCb(pStatus, "Invalid packet argument")); + + enum AVMediaType eType = pStream->codecInfo.mediaType; + const char* pType = (eType == AVMEDIA_TYPE_AUDIO) ? "audio" : "video"; + + if (pStream == NULL) + { + pStream = XStreams_GetByDstIndex(&pEncoder->streams, pPacket->stream_index); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: %d", pPacket->stream_index)); + } + + if (pEncoder->nTSFix && + (pStream->nLastPTS >= pPacket->pts || + pStream->nLastDTS >= pPacket->dts)) + { + pPacket->pts = pStream->nLastPTS + pEncoder->nTSFix; + pPacket->dts = pStream->nLastDTS + pEncoder->nTSFix; + + XStat_InfoCb(pStatus, "Fixed %s TS: PTS(%lld), DTS(%lld)", + pType, pPacket->pts, pPacket->dts); + + return XSTDOK; + } + + return XSTDNON; +} + +XSTATUS XEncoder_WritePacket(xencoder_t *pEncoder, AVPacket *pPacket) +{ + XASSERT_RET(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT(pPacket, XStat_ErrCb(pStatus, "Invalid packet argument")); + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Encoder format context is not init")); + XASSERT(pEncoder->bOutputOpen, XStat_ErrCb(pStatus, "Output context is not open")); + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, pPacket->stream_index); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: %d", pPacket->stream_index)); + XASSERT(pStream->pAvStream, XStat_ErrCb(pStatus, "Stream is not open: %d", pStream->nDstIndex)); + + /* Rescale timestamps and fix non motion PTS/DTS if detected */ + XEncoder_RescaleTS(pEncoder, pPacket, pStream); + XEncoder_FixTS(pEncoder, pPacket, pStream); + + pStream->nLastPTS = pPacket->pts; + pStream->nLastDTS = pPacket->dts; + + pStatus->nAVStatus = av_interleaved_write_frame(pEncoder->pFmtCtx, pPacket); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to write packet")); + + pStream->nPacketCount++; + return XSTDOK; +} + +XSTATUS XEncoder_WriteFrame(xencoder_t *pEncoder, AVFrame *pFrame, int nStreamIndex) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, nStreamIndex); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: dst(%d)", nStreamIndex)); + XASSERT(pStream->bCodecOpen, XStat_ErrCb(pStatus, "Codec is not open: dst(%d)", nStreamIndex)); + + AVPacket* pPacket = XStream_GetOrCreatePacket(pStream); + XASSERT(pPacket, XStat_ErrCb(pStatus, "Failed to allocate packet: %s", strerror(errno))); + + pStatus->nAVStatus = avcodec_send_frame(pStream->pCodecCtx, pFrame); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to send frame to encoder: dst(%d)", nStreamIndex)); + + while (pStatus->nAVStatus >= 0) + { + pStatus->nAVStatus = avcodec_receive_packet(pStream->pCodecCtx, pPacket); + if (pStatus->nAVStatus == AVERROR(EAGAIN) || pStatus->nAVStatus == AVERROR_EOF) return XSTDOK; + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to encode packet: dst(%d)", nStreamIndex)); + + pPacket->stream_index = nStreamIndex; + int nRetVal = XSTDOK; + + if (pEncoder->packetCallback != NULL) + { + nRetVal = pEncoder->packetCallback(pEncoder->pUserCtx, pPacket); + XASSERT((nRetVal >= 0), XStat_ErrCb(pStatus, "User terminated packet encoding")); + } + + if (nRetVal > 0) + { + XEncoder_WritePacket(pEncoder, pPacket); + XASSERT_RET((pStatus->nAVStatus >= 0), XSTDERR); + } + + /* Recycle packet */ + av_packet_unref(pPacket); + } + + return XSTDOK; +} + +XSTATUS XEncoder_WriteFrame2(xencoder_t *pEncoder, AVFrame *pFrame, xframe_params_t *pParams) +{ + XASSERT((pEncoder && pFrame && pParams), XSTDINV); + XASSERT((pParams->nIndex >= 0), XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + if (pParams->mediaType == AVMEDIA_TYPE_VIDEO) + { + enum AVPixelFormat pixFmt = (enum AVPixelFormat)pFrame->format; + int nHeight = pFrame->height; + int nWidth = pFrame->width; + xbool_t bRescale = XFALSE; + + if ((pParams->nWidth > 0 && pParams->nHeight > 0) && + (pFrame->width != pParams->nWidth || + pFrame->height != pParams->nHeight)) + { + nWidth = pParams->nWidth; + nHeight = pParams->nHeight; + bRescale = XTRUE; + } + else if (pFrame->width > pFrame->linesize[0] || + pFrame->width / 2 > pFrame->linesize[1] || + pFrame->width / 2 > pFrame->linesize[2]) + { + nWidth = pFrame->width; + nHeight = pFrame->height; + bRescale = XTRUE; + } + + if (pParams->pixFmt != AV_PIX_FMT_NONE && + pParams->pixFmt != pixFmt) + { + pixFmt = pParams->pixFmt; + bRescale = XTRUE; + } + + if (bRescale) + { + pParams->pixFmt = pixFmt; + pParams->nWidth = nWidth; + pParams->nHeight = nHeight; + + AVFrame *pAvFrame = XFrame_NewScale(pFrame, pParams); + XASSERT(pAvFrame, xthrow("Failed to scale frame")); + int64_t nPTS = pAvFrame->pts; + + pStatus->nAVStatus = XEncoder_WriteFrame(pEncoder, pAvFrame, pParams->nIndex); + XASSERT_CALL((pStatus->nAVStatus > 0), av_frame_free, &pAvFrame, XStat_ErrCb(pStatus, + "Video encoding failed: pts(%lld), dst(%d)", nPTS, pParams->nIndex)); + + av_frame_free(&pAvFrame); + return XSTDOK; + } + } + else if (pParams->mediaType == AVMEDIA_TYPE_AUDIO) + { + enum AVSampleFormat sampleFmt = (enum AVSampleFormat)pFrame->format; + int nChannels = XFrame_GetChannelCount(pFrame); + int nSampleRate = pFrame->sample_rate; + xbool_t bResample = XFALSE; + + if (pParams->nSampleRate > 0 && + pParams->nSampleRate != nSampleRate) + { + nSampleRate = pParams->nSampleRate; + bResample = XTRUE; + } + + if (pParams->sampleFmt != AV_SAMPLE_FMT_NONE && + pParams->sampleFmt != sampleFmt) + { + sampleFmt = pParams->sampleFmt; + bResample = XTRUE; + } + + if (pParams->nChannels > 0 && + pParams->nChannels != nChannels) + { + nChannels = pParams->nChannels; + bResample = XTRUE; + } + + if (bResample) + { + pParams->nSampleRate = nSampleRate; + pParams->sampleFmt = sampleFmt; + pParams->nChannels = nChannels; + + AVFrame *pAvFrame = XFrame_NewResample(pFrame, pParams); + XASSERT(pAvFrame, XStat_ErrCb(pStatus, "Failed to resample frame")); + int64_t nPTS = pAvFrame->pts; + + pStatus->nAVStatus = XEncoder_WriteFrame(pEncoder, pAvFrame, pParams->nIndex); + XASSERT_CALL((pStatus->nAVStatus > 0), av_frame_free, &pAvFrame, XStat_ErrCb(pStatus, + "Audio encoding failed: pts(%lld), dst(%d)", nPTS, pParams->nIndex)); + + av_frame_free(&pAvFrame); + return XSTDOK; + } + } + + pStatus->nAVStatus = XEncoder_WriteFrame(pEncoder, pFrame, pParams->nIndex); + XASSERT((pStatus->nAVStatus > 0), XStat_ErrCb(pStatus, + "Encoding failed: pts(%lld), dst(%d)", pFrame->pts, pParams->nIndex)); + + return XSTDOK; +} + +XSTATUS XEncoder_WriteFrame3(xencoder_t *pEncoder, AVFrame *pFrame, int nStreamIndex) +{ + XASSERT((pEncoder && pFrame), XSTDINV); + XASSERT((nStreamIndex >= 0), XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + const xcodec_t *pCodecInfo = XEncoder_GetCodecInfo(pEncoder, nStreamIndex); + XASSERT(pCodecInfo, XStat_ErrCb(pStatus, "Failed to get output codec info: dst(%d)", nStreamIndex)); + + xframe_params_t params; + XFrame_InitParams(¶ms, NULL); + + params.nIndex = nStreamIndex; + params.mediaType = pCodecInfo->mediaType; + params.status.cb = pEncoder->status.cb; + params.status.nTypes = pEncoder->status.nTypes; + params.status.pUserCtx = pEncoder->status.pUserCtx; + + if (params.mediaType == AVMEDIA_TYPE_VIDEO) + { + params.scaleFmt = pCodecInfo->scaleFmt; + params.pixFmt = pCodecInfo->pixFmt; + params.nWidth = pCodecInfo->nWidth; + params.nHeight = pCodecInfo->nHeight; + } + else if (params.mediaType == AVMEDIA_TYPE_AUDIO) + { + params.nSampleRate = pCodecInfo->nSampleRate; + params.sampleFmt = pCodecInfo->sampleFmt; + params.nChannels = pCodecInfo->nChannels; + } + + return XEncoder_WriteFrame2(pEncoder, pFrame, ¶ms); +} + +XSTATUS XEncoder_AddChapter(xencoder_t *pEncoder, AVChapter *pChapter) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT(pChapter, XStat_ErrCb(pStatus, "Invalid chapter argument")); + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Invalid format context")); + + size_t nChapterTotal = pEncoder->pFmtCtx->nb_chapters + 1; + size_t nElementSize = sizeof(*pEncoder->pFmtCtx->chapters); + + AVChapter **pTmp = av_realloc_f(pEncoder->pFmtCtx->chapters, nChapterTotal, nElementSize); + XASSERT(pTmp, XStat_ErrCb(pStatus, "Failed to reallocate output contetxt chapters")); + pEncoder->pFmtCtx->chapters = pTmp; + + pEncoder->pFmtCtx->chapters[pEncoder->pFmtCtx->nb_chapters++] = pChapter; + return XSTDOK; +} + +XSTATUS XEncoder_AddChapters(xencoder_t *pEncoder, xarray_t *pChapters) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT(pChapters, XStat_ErrCb(pStatus, "Invalid chapters argument")); + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Invalid format context")); + + size_t i, nChapterStored = 0, nChapterCount = XArray_Used(pChapters); + XASSERT(nChapterCount, XStat_ErrCb(pStatus, "Empty chapter array")); + + size_t nChapterTotal = pEncoder->pFmtCtx->nb_chapters + nChapterCount; + size_t nElementSize = sizeof(*pEncoder->pFmtCtx->chapters); + + AVChapter **pTmp = av_realloc_f(pEncoder->pFmtCtx->chapters, nChapterTotal, nElementSize); + XASSERT(pTmp, XStat_ErrCb(pStatus, "Failed to reallocate output contetxt chapters")); + pEncoder->pFmtCtx->chapters = pTmp; + + for (i = 0; i < nChapterCount; i++) + { + AVChapter *pChapter = (AVChapter*)XArray_GetData(pChapters, i); + if (pChapter == NULL) continue; + + pEncoder->pFmtCtx->chapters[pEncoder->pFmtCtx->nb_chapters++] = pChapter; + nChapterStored++; + } + + return nChapterStored; +} + +XSTATUS XEncoder_AddMeta(xencoder_t *pEncoder, xmeta_t *pMeta) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + XASSERT(pMeta, XStat_ErrCb(pStatus, "Invalid meta argument")); + XASSERT(pEncoder->pFmtCtx, XStat_ErrCb(pStatus, "Invalid format context")); + + if (pMeta->chapters.nUsed > 0) + { + if (XEncoder_AddChapters(pEncoder, &pMeta->chapters) > 0) + pMeta->chapters.clearCb = NULL; /* Encoder owns chapters now */ + } + + if (pMeta->pData != NULL) + { + pEncoder->pFmtCtx->metadata = pMeta->pData; + pMeta->pData = NULL; /* Encoder owns metadata now */ + } + + return XSTDOK; +} + +XSTATUS XEncoder_FlushBuffer(xencoder_t *pEncoder, int nStreamIndex) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, nStreamIndex); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: dst(%d)", nStreamIndex)); + + XASSERT((pStream->pCodecCtx && pStream->bCodecOpen), + XStat_ErrCb(pStatus, "Codec is not open: dst(%d)", nStreamIndex)); + + XStream_FlushBuffers(pStream); + return XSTDOK; +} + +XSTATUS XEncoder_FlushBuffers(xencoder_t *pEncoder) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + size_t i, nCount = XArray_Used(&pEncoder->streams); + XStat_InfoCb(pStatus, "Flushing streams: count(%d),", nCount); + + for (i = 0; i < pEncoder->streams.nUsed; i++) + { + xstream_t *pStream = XStreams_GetByIndex(&pEncoder->streams, i); + if (pStream != NULL) XStream_FlushBuffers(pStream); + } + + return XSTDOK; +} + +XSTATUS XEncoder_FlushStream(xencoder_t *pEncoder, int nStreamIndex) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + xstream_t *pStream = XStreams_GetByDstIndex(&pEncoder->streams, nStreamIndex); + XASSERT(pStream, XStat_ErrCb(pStatus, "Stream is not found: dst(%d)", nStreamIndex)); + + XASSERT((pStream->pCodecCtx && pStream->bCodecOpen), + XStat_ErrCb(pStatus, "Codec is not open: dst(%d)", nStreamIndex)); + + XEncoder_WriteFrame(pEncoder, NULL, pStream->nDstIndex); + return XSTDOK; +} + +XSTATUS XEncoder_FlushStreams(xencoder_t *pEncoder) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + + size_t i, nCount = XArray_Used(&pEncoder->streams); + XStat_InfoCb(pStatus, "Flushing streams: count(%d),", nCount); + + for (i = 0; i < nCount; i++) + { + xstream_t *pStream = XStreams_GetByIndex(&pEncoder->streams, i); + if (!pStream || !pStream->pCodecCtx || !pStream->bCodecOpen) continue; + + XEncoder_WriteFrame(pEncoder, NULL, pStream->nDstIndex); + } + + return XSTDOK; +} + +XSTATUS XEncoder_FinishWrite(xencoder_t *pEncoder, xbool_t bFlush) +{ + XASSERT(pEncoder, XSTDINV); + xstatus_t *pStatus = &pEncoder->status; + if (bFlush) XEncoder_FlushStreams(pEncoder); + + /* Write trailer */ + if (pEncoder->pFmtCtx != NULL) + { + const char *pFmt = xstrused(pEncoder->sOutFormat) ? pEncoder->sOutFormat : "N/A"; + XStat_InfoCb(pStatus, "Writing trailer: fmt(%s), url(%s)", pFmt, pEncoder->sOutputPath); + av_write_trailer(pEncoder->pFmtCtx); + } + + return XSTDOK; +} diff --git a/src/encoder.h b/src/encoder.h new file mode 100644 index 0000000..47a1f64 --- /dev/null +++ b/src/encoder.h @@ -0,0 +1,105 @@ +/*! + * @file libxmedia/src/encoder.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio encoding + * functionality based on the FFMPEG API and libraries. + */ + +#ifndef __XMEDIA_ENCODER_H__ +#define __XMEDIA_ENCODER_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "status.h" +#include "stream.h" +#include "frame.h" +#include "meta.h" + +typedef void(*xencoder_stat_cb_t)(void *pUserCtx, const char *pStatus); +typedef void(*xencoder_err_cb_t)(void *pUserCtx, const char *pErrStr); +typedef int(*xencoder_pkt_cb_t)(void *pUserCtx, AVPacket *pPacket); +typedef int(*xmuxer_cb_t)(void *pUserCtx, uint8_t *pData, int nSize); + +#define XENCODER_IO_SIZE (1024 * 64) + +typedef enum { + XPTS_CALCULATE, // Calculate timestamps based on the elapsed time and clock rate + XPTS_COMPUTE, // Compute timestamps based on the sample rate and time base + XPTS_RESCALE, // Rescale original timestamps using av_packet_rescale_ts() + XPTS_ROUND, // Rescale original timestamps and round to the nearest value + XPTS_SOURCE, // Use original timestamps from the source stream + XPTS_INVALID // Invalid PTS/DTS calculation type +} xpts_ctl_t; + +typedef struct xencoder_ { + /* Encoder/muxer context */ + AVFormatContext* pFmtCtx; + AVIOContext* pIOCtx; + xarray_t streams; + + /* IO buffer context */ + char sOutputPath[XPATH_MAX]; + char sOutFormat[XSTR_TINY]; + size_t nIOBuffSize; + uint8_t* pIOBuffer; + + /* User input flags and params */ + xencoder_stat_cb_t statusCallback; + xencoder_err_cb_t errorCallback; + xencoder_pkt_cb_t packetCallback; + xmuxer_cb_t muxerCallback; + xbool_t bMuxOnly; + FILE* pOutFile; + void* pUserCtx; + + /* Timestamp calculation stuff */ + xpts_ctl_t eTSType; + uint64_t nStartTime; + int nTSFix; + + /* FFMPEG status flag */ + xbool_t bOutputOpen; + xstatus_t status; +} xencoder_t; + +void XEncoder_Init(xencoder_t *pEncoder); +void XEncoder_Destroy(xencoder_t *pEncoder); + +XSTATUS XEncoder_OpenFormat(xencoder_t *pEncoder, const char *pFormat, const char *pOutputUrl); +XSTATUS XEncoder_GuessFormat(xencoder_t *pEncoder, const char *pFormat, const char *pOutputUrl); +XSTATUS XEncoder_OpenStream(xencoder_t *pEncoder, xcodec_t *pCodecInfo); +XSTATUS XEncoder_OpenOutput(xencoder_t *pEncoder, AVDictionary *pOpts); + +XSTATUS XEncoder_RestartCodec(xencoder_t *pEncoder, int nStreamIndex); +XSTATUS XEncoder_FlushStream(xencoder_t *pEncoder, int nStreamIndex); +XSTATUS XEncoder_FlushBuffer(xencoder_t *pEncoder, int nStreamIndex); +XSTATUS XEncoder_FlushStreams(xencoder_t *pEncoder); +XSTATUS XEncoder_FlushBuffers(xencoder_t *pEncoder); + +const xcodec_t* XEncoder_GetCodecInfo(xencoder_t* pEncoder, int nStreamIndex); +XSTATUS XEncoder_CopyCodecInfo(xencoder_t* pEncoder, xcodec_t *pCodecInfo, int nStreamIndex); +XSTATUS XEncoder_RescaleTS(xencoder_t *pEncoder, AVPacket *pPacket, xstream_t *pStream); +XSTATUS XEncoder_FixTS(xencoder_t *pEncoder, AVPacket *pPacket, xstream_t *pStream); + +XSTATUS XEncoder_WriteFrame3(xencoder_t *pEncoder, AVFrame *pFrame, int nStreamIndex); +XSTATUS XEncoder_WriteFrame2(xencoder_t *pEncoder, AVFrame *pFrame, xframe_params_t *pParams); +XSTATUS XEncoder_WriteFrame(xencoder_t *pEncoder, AVFrame *pFrame, int nStreamIndex); +XSTATUS XEncoder_WriteHeader(xencoder_t *pEncoder, AVDictionary *pHeaderOpts); +XSTATUS XEncoder_WritePacket(xencoder_t *pEncoder, AVPacket *pPacket); +XSTATUS XEncoder_FinishWrite(xencoder_t *pEncoder, xbool_t bFlush); + +XSTATUS XEncoder_AddMeta(xencoder_t *pEncoder, xmeta_t *pMeta); +XSTATUS XEncoder_AddChapter(xencoder_t *pEncoder, AVChapter *pChapter); +XSTATUS XEncoder_AddChapters(xencoder_t *pEncoder, xarray_t *pChapters); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_ENCODER_H__ */ diff --git a/src/frame.c b/src/frame.c new file mode 100644 index 0000000..228fbdc --- /dev/null +++ b/src/frame.c @@ -0,0 +1,517 @@ +/*! + * @file libxmedia/src/frame.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio frame + * functionality based on the FFMPEG API and libraries. + */ + +#include "frame.h" +#include "codec.h" + +#define XFRAME_SET_INT(dst, src) if (src >= 0) dst = src +#define XFRAME_SET_INT2(dst, src, src2) if (src >= 0) dst = src; else dst = src2 + +void XFrame_InitFrame(AVFrame* pFrame) +{ + bzero(pFrame, sizeof(AVFrame)); + av_frame_unref(pFrame); +} + +void XFrame_InitChannels(AVFrame *pFrame, int nChannels) +{ +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_default(&pFrame->ch_layout, nChannels); +#else + if (nChannels == 1) pFrame->channel_layout = AV_CH_LAYOUT_MONO; + else pFrame->channel_layout = AV_CH_LAYOUT_STEREO; + pFrame->channels = nChannels; +#endif +} + +int XFrame_GetChannelCount(AVFrame *pFrame) +{ +#ifdef XCODEC_USE_NEW_CHANNEL + return pFrame->ch_layout.nb_channels; +#else + return pFrame->channels; +#endif +} + +xscale_fmt_t XFrame_GetScaleFmt(const char* pFmtName) +{ + XASSERT_RET(pFmtName, XSCALE_FMT_NONE); + + if (!strncmp(pFmtName, "stretch", 7)) return XSCALE_FMT_STRETCH; + else if (!strncmp(pFmtName, "aspect", 6)) return XSCALE_FMT_ASPECT; + + return XSCALE_FMT_NONE; +} + +void XFrame_InitParams(xframe_params_t *pFrame, xframe_params_t *pParent) +{ + /* Audio parameters */ + pFrame->sampleFmt = AV_SAMPLE_FMT_NONE; + pFrame->nSampleRate = XSTDERR; + pFrame->nChannels = XSTDERR; + + /* Video parameters */ + pFrame->pixFmt = AV_PIX_FMT_NONE; + pFrame->scaleFmt = XSCALE_FMT_NONE; + pFrame->nWidth = XSTDERR; + pFrame->nHeight = XSTDERR; + + pFrame->mediaType = AVMEDIA_TYPE_UNKNOWN; + pFrame->nIndex = XSTDERR; + pFrame->nPTS = XSTDERR; + + /* General parameters */ + if (pParent) XStat_InitFrom(&pFrame->status, &pParent->status); + else XStat_Init(&pFrame->status, XSTDNON, NULL, NULL); +} + +XSTATUS XFrame_Resample(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, XSTDINV); + xstatus_t *pStatus = &pParams->status; + + XASSERT((pFrameOut != NULL && pFrameIn != NULL), + XStat_ErrCb(pStatus, "Invalid resample frames: in(%p), out(%p)", + (void*)pFrameIn, (void*)pFrameOut)); + + XASSERT((pParams->sampleFmt != AV_SAMPLE_FMT_NONE), + XStat_ErrCb(pStatus, "Invalid sample format: fmt(%d)", + (int)pParams->sampleFmt)); + + XASSERT((pParams->nSampleRate > 0 && pParams->nChannels > 0), + XStat_ErrCb(pStatus, "Invalid sample rate or channels: sr(%d), ch(%d)", + pParams->nSampleRate, pParams->nChannels)); + + struct SwrContext *pSwrCtx = NULL; + enum AVSampleFormat srcFmt = (enum AVSampleFormat)pFrameIn->format; + +#ifdef XCODEC_USE_NEW_CHANNEL + av_channel_layout_default(&pFrameOut->ch_layout, pParams->nChannels); + + pStatus->nAVStatus = swr_alloc_set_opts2(&pSwrCtx, + &pFrameOut->ch_layout, pParams->sampleFmt, pParams->nSampleRate, + &pFrameIn->ch_layout, srcFmt, pFrameIn->sample_rate, 0, NULL); + + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, "Failed to get or create SWR context")); +#else + /* Get or create swr context with in/out frame parameters */ + pFrameOut->channel_layout = av_get_default_channel_layout(pParams->nChannels); + pFrameOut->channels = pParams->nChannels; + + pSwrCtx = swr_alloc_set_opts(NULL, + pFrameOut->channel_layout, + pParams->sampleFmt, pParams->nSampleRate, + av_get_default_channel_layout(pFrameIn->channels), + srcFmt, pFrameIn->sample_rate, 0, NULL); + + XASSERT(pSwrCtx, XStat_ErrCb(pStatus, "Failed to get or create SWR context")); +#endif + + int64_t nSwrDelay = swr_get_delay(pSwrCtx, pFrameIn->sample_rate); + pFrameOut->nb_samples = av_rescale_rnd(nSwrDelay + pFrameIn->nb_samples, + pParams->nSampleRate, pFrameIn->sample_rate, AV_ROUND_UP); + + pFrameOut->format = pParams->sampleFmt; + pFrameOut->sample_rate = pParams->nSampleRate; + XFRAME_SET_INT2(pFrameOut->pts, pParams->nPTS, pFrameIn->pts); + + /* Initialize the resampling context */ + pStatus->nAVStatus = swr_init(pSwrCtx); + XASSERT_CALL((pStatus->nAVStatus >= 0), swr_free, &pSwrCtx, + XStat_ErrCb(pStatus, "Failed to initialize the SWR context")); + + /* Allocate AVFrame buffer */ + pStatus->nAVStatus = av_frame_get_buffer(pFrameOut, 0); + XASSERT_CALL((pStatus->nAVStatus >= 0), swr_free, &pSwrCtx, + XStat_ErrCb(pStatus, "Failed to get buffer for AVFrame")); + + XStat_DebugCb(pStatus, "Resampling frame: sample rate(%d -> %d), pts(%lld)", + pFrameIn->sample_rate, pParams->nSampleRate, pFrameOut->pts); + + /* Resample frames */ + pStatus->nAVStatus = swr_convert(pSwrCtx, pFrameOut->data, pFrameOut->nb_samples, + (const uint8_t **)pFrameIn->data, pFrameIn->nb_samples); + + XASSERT_CALL((pStatus->nAVStatus >= 0), swr_free, &pSwrCtx, + XStat_ErrCb(pStatus, "SWR failed to resample AVFrame")); + + swr_free(&pSwrCtx); + return XSTDOK; +} + +AVFrame* XFrame_FromOpus(uint8_t *pOpusBuff, size_t nSize, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Validate input arguments */ + XASSERT((pOpusBuff && nSize), XStat_ErrPtr(pStatus, "Invalid frame buffer or size")); + XASSERT((pParams->nSampleRate > 0), XStat_ErrPtr(pStatus, "Invalid frame sample rate")); + XASSERT((pParams->nChannels > 0), XStat_ErrPtr(pStatus, "Invalid frame channel count")); + + /* Allocate AVFrame */ + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Calculate the number of samples in the decoded buffer */ + enum AVSampleFormat sampleFmt = AV_SAMPLE_FMT_S16; + int nSampleSize = av_get_bytes_per_sample(sampleFmt); + int nNumSamples = nSize / (pParams->nChannels * nSampleSize); + int nTotalSamples = nNumSamples * pParams->nChannels; + + XFrame_InitChannels(pFrameOut, pParams->nChannels); + XFRAME_SET_INT(pFrameOut->pts, pParams->nPTS); + pFrameOut->sample_rate = pParams->nSampleRate; + pFrameOut->nb_samples = nNumSamples; + pFrameOut->format = sampleFmt; + + pStatus->nAVStatus = av_samples_alloc(pFrameOut->data, pFrameOut->linesize, + pParams->nChannels, pFrameOut->nb_samples, sampleFmt, 0); + + XASSERT_CALL((pStatus->nAVStatus >= 0), av_frame_free, &pFrameOut, + XStat_ErrPtr(pStatus, "Failed to alloc AVFrame sample buffer")); + + /* Copy Opus data from raw buffer to the AVFrame */ + uint8_t *pDstBuff = pFrameOut->data[0]; + memcpy(pDstBuff, pOpusBuff, nTotalSamples * nSampleSize); + + return pFrameOut; +} + +AVFrame* XFrame_FromYUV(uint8_t *pYUVBuff, size_t nSize, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Validate input arguments */ + XASSERT((pYUVBuff && nSize), XStat_ErrPtr(pStatus, "Invalid YUV buffer or size")); + XASSERT((pParams->nWidth > 0 && pParams->nHeight > 0), + XStat_ErrPtr(pStatus, "Invalid frame resolution")); + + size_t nExpectedSize = pParams->nWidth * pParams->nHeight; + nExpectedSize += (pParams->nWidth * pParams->nHeight / 4); + nExpectedSize += (pParams->nWidth * pParams->nHeight / 4); + + XASSERT((nSize == nExpectedSize), XStat_ErrPtr(pStatus, + "Invalid frame size: expected(%zu), have(%zu)", nExpectedSize, nSize)); + + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Setup frame parameters */ + XFRAME_SET_INT(pFrameOut->pts, pParams->nPTS); + pParams->pixFmt = AV_PIX_FMT_YUV420P; + pFrameOut->format = pParams->pixFmt; + pFrameOut->width = pParams->nWidth; + pFrameOut->height = pParams->nHeight; + + /* Allocate YUV frame buffer */ + pStatus->nAVStatus = av_image_alloc(pFrameOut->data, pFrameOut->linesize, + pParams->nWidth, pParams->nHeight, pParams->pixFmt, 32); + + XASSERT_CALL((pStatus->nAVStatus >= 0), av_frame_free, &pFrameOut, + XStat_ErrPtr(pStatus, "Failed to allocate AV image frame buffer")); + + const uint8_t *pSlices[4] = {0}; + int nLineSizes[4] = {0}; + + /* Set pointers to the Y, U, and V planes of the source YUV buffer */ + pSlices[0] = pYUVBuff; + pSlices[1] = pSlices[0] + pParams->nWidth * pParams->nHeight; + pSlices[2] = pSlices[1] + (pParams->nWidth * pParams->nHeight) / 4; + + /* Set the linesizes for each plane in the source YUV buffer */ + nLineSizes[0] = pParams->nWidth; + nLineSizes[1] = pParams->nWidth / 2; + nLineSizes[2] = pParams->nWidth / 2; + + /* Copy the source YUV data into the allocated AVFrame buffer */ + av_image_copy(pFrameOut->data, pFrameOut->linesize, + pSlices, nLineSizes, pParams->pixFmt, + pParams->nWidth, pParams->nHeight); + + return pFrameOut; +} + +XSTATUS XFrame_BlackYUV(AVFrame *pFrameOut, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, XSTDINV); + xstatus_t *pStatus = &pParams->status; + + XASSERT((pFrameOut != NULL), XStat_ErrCb(pStatus, "Invalid YUV output frame argument")); + XASSERT((pParams->nWidth && pParams->nHeight), XStat_ErrCb(pStatus, "Invalid YUV resolution")); + + XFRAME_SET_INT(pFrameOut->pts, pParams->nPTS); + pParams->pixFmt = AV_PIX_FMT_YUV420P; + pFrameOut->format = pParams->pixFmt; + pFrameOut->width = pParams->nWidth; + pFrameOut->height = pParams->nHeight; + + pStatus->nAVStatus = av_frame_get_buffer(pFrameOut, 0); + XASSERT((pStatus->nAVStatus >= 0), XStat_ErrCb(pStatus, + "Failed to allocate memory for AVFrame buffer")); + + pStatus->nAVStatus = av_frame_make_writable(pFrameOut); + XASSERT_CALL((pStatus->nAVStatus >= 0), av_frame_unref, pFrameOut, + XStat_ErrCb(pStatus, "Failed to make AVFrame writeable")); + + /* Fill Y plane with black (0x00) and U/V planes with neutral chroma value (0x80) */ + memset(pFrameOut->data[0], 0x00, pFrameOut->linesize[0] * pParams->nHeight); + memset(pFrameOut->data[1], 0x80, pFrameOut->linesize[1] * (pParams->nHeight / 2)); + memset(pFrameOut->data[2], 0x80, pFrameOut->linesize[2] * (pParams->nHeight / 2)); + + return XSTDOK; +} + +XSTATUS XFrame_OverlayYUV(AVFrame *pDstFrame, AVFrame *pSrcFrame, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, XSTDINV); + xstatus_t *pStatus = &pParams->status; + + XASSERT((pDstFrame && pSrcFrame), XStat_ErrCb(pStatus, "Invalid overlay src/dst frame arguments")); + XASSERT((pSrcFrame->width && pSrcFrame->height), XStat_ErrCb(pStatus, "Invalid src frame resolution")); + XASSERT((pDstFrame->width && pDstFrame->height), XStat_ErrCb(pStatus, "Invalid dst frame resolution")); + + XASSERT((pDstFrame->height >= pSrcFrame->height && pDstFrame->width >= pSrcFrame->width), + XStat_ErrCb(pStatus, "Overlay src is bigger than dst: src(%dx%d), dst(%dx%d)", + pSrcFrame->width, pSrcFrame->height, pDstFrame->width, pDstFrame->height)); + + pParams->pixFmt = AV_PIX_FMT_YUV420P; + pDstFrame->format = pParams->pixFmt; + + int i, nSrcWidth = pSrcFrame->width; + int nSrcHeight = pSrcFrame->height; + + int nOffsetX = (pDstFrame->width - nSrcWidth) / 2; + int nOffsetY = (pDstFrame->height - nSrcHeight) / 2; + + /* Copy Y (luma) plane */ + for (i = 0; i < nSrcHeight; i++) + { + int y = i + nOffsetY; + memcpy(pDstFrame->data[0] + y * pDstFrame->linesize[0] + nOffsetX, + pSrcFrame->data[0] + i * pSrcFrame->linesize[0], nSrcWidth); + } + + /* Copy U and V (chroma) planes */ + nSrcWidth /= 2; nSrcHeight /= 2; + nOffsetX /= 2; nOffsetY /= 2; + + for (i = 0; i < nSrcHeight; i++) + { + int y = i + nOffsetY; + memcpy(pDstFrame->data[1] + y * pDstFrame->linesize[1] + nOffsetX, + pSrcFrame->data[1] + i * pSrcFrame->linesize[1], nSrcWidth); + + memcpy(pDstFrame->data[2] + y * pDstFrame->linesize[2] + nOffsetX, + pSrcFrame->data[2] + i * pSrcFrame->linesize[2], nSrcWidth); + } + + return XSTDOK; +} + +XSTATUS XFrame_Stretch(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, XSTDINV); + xstatus_t *pStatus = &pParams->status; + + XASSERT((pFrameOut && pFrameIn), XStat_ErrCb(pStatus, "Invalid scale in/out frame arguments")); + XASSERT((pParams->pixFmt != AV_PIX_FMT_NONE), XStat_ErrCb(pStatus, "Invalid pixel format")); + XASSERT((pParams->nWidth && pParams->nHeight), XStat_ErrCb(pStatus, "Invalid scale resolution")); + + struct SwsContext *pSwsCtx = NULL; + enum AVPixelFormat srcFmt = (enum AVPixelFormat)pFrameIn->format; + + /* Get or create sws context with in/out frame parameters */ + pSwsCtx = sws_getCachedContext(NULL, pFrameIn->width, pFrameIn->height, + srcFmt, pParams->nWidth, pParams->nHeight, + pParams->pixFmt, SWS_BICUBIC, NULL, NULL, NULL); + XASSERT(pSwsCtx, XStat_ErrCb(pStatus, "Failed to get or create SWS context")); + + /* Setup out frame properties */ + XFRAME_SET_INT2(pFrameOut->pts, pParams->nPTS, pFrameIn->pts); + pFrameOut->width = pParams->nWidth; + pFrameOut->height = pParams->nHeight; + pFrameOut->format = pParams->pixFmt; + pFrameOut->pkt_dts = pFrameIn->pkt_dts; + int nSrcSliceY = 0; + + /* Allocate AVFrame buffer */ + pStatus->nAVStatus = av_frame_get_buffer(pFrameOut, 0); + XASSERT_CALL((pStatus->nAVStatus >= 0), sws_freeContext, pSwsCtx, + XStat_ErrCb(pStatus, "Failed to get buffer for AVFrame")); + + XStat_DebugCb(pStatus, "Scaling frame: in(%dx%d), out(%dx%d), pts(%lld)", + pFrameIn->width, pFrameIn->height, pParams->nWidth, + pParams->nHeight, pFrameOut->pts); + + /* Scale destination frame */ + sws_scale(pSwsCtx, (const uint8_t* const*)pFrameIn->data, pFrameIn->linesize, + nSrcSliceY, pFrameIn->height, pFrameOut->data, pFrameOut->linesize); + + sws_freeContext(pSwsCtx); + return XSTDOK; +} + +XSTATUS XFrame_Aspect(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, XSTDINV); + xstatus_t *pStatus = &pParams->status; + + XASSERT((pFrameOut && pFrameIn), XStat_ErrCb(pStatus, "Invalid scale in/out frame arguments")); + XASSERT((pParams->pixFmt != AV_PIX_FMT_NONE), XStat_ErrCb(pStatus, "Invalid pixel format")); + XASSERT((pParams->nWidth && pParams->nHeight), XStat_ErrCb(pStatus, "Invalid scale resolution")); + + AVFrame scaledFrame; + XFrame_InitFrame(&scaledFrame); + + xframe_params_t scaleParams; + XFrame_InitParams(&scaleParams, pParams); + + /* Calculate the scaling factors for width and height */ + float widthScaleFactor = (float)pParams->nWidth / pFrameIn->width; + float heightScaleFactor = (float)pParams->nHeight / pFrameIn->height; + + /* Choose the smaller scaling factor to fit both dimensions */ + float scaleFactor = (widthScaleFactor < heightScaleFactor) ? + widthScaleFactor : heightScaleFactor; + + /* Calculate the scaled width and height */ + scaleParams.nWidth = (int)(pFrameIn->width * scaleFactor); + scaleParams.nHeight = (int)(pFrameIn->height * scaleFactor); + scaleParams.pixFmt = pParams->pixFmt; + scaleParams.nPTS = pParams->nPTS; + + if (scaleParams.nWidth == pParams->nWidth && + scaleParams.nHeight == pParams->nHeight) + return XFrame_Stretch(pFrameOut, pFrameIn, pParams); + + XStat_DebugCb(pStatus, "Corrected aspect: in(%dx%d), ar(%dx%d), out(%dx%d), pts(%lld)", + pFrameIn->width, pFrameIn->height, scaleParams.nWidth, scaleParams.nHeight, + pParams->nWidth, pParams->nHeight, pParams->nPTS); + + /* Create YUV420P scaled frame for overlay */ + scaleParams.pixFmt = AV_PIX_FMT_YUV420P; + pParams->pixFmt = AV_PIX_FMT_YUV420P; + + int nStatus = XFrame_Stretch(&scaledFrame, pFrameIn, &scaleParams); + XASSERT((nStatus > 0), XStat_ErrCb(pStatus, "Failed to stretch frame")); + + nStatus = XFrame_BlackYUV(pFrameOut, pParams); + XASSERT_CALL((nStatus > 0), av_frame_unref, &scaledFrame, + XStat_ErrCb(pStatus, "Failed to create black YUV frame")); + + nStatus = XFrame_OverlayYUV(pFrameOut, &scaledFrame, pParams); + XASSERT_CALL((nStatus > 0), av_frame_unref, &scaledFrame, + XStat_ErrCb(pStatus, "Failed to overlay YUV frame")); + + XFRAME_SET_INT2(pFrameOut->pts, pParams->nPTS, pFrameIn->pts); + pFrameOut->pkt_dts = pFrameIn->pkt_dts; + + av_frame_unref(&scaledFrame); + return XSTDOK; +} + +XSTATUS XFrame_Scale(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, XSTDINV); + xstatus_t *pStatus = &pParams->status; + XSTATUS nStatus = XSTDINV; + + XASSERT((pParams->scaleFmt != XSCALE_FMT_NONE), + XStat_ErrCb(pStatus, "Invalid scale format")); + + if (pParams->scaleFmt == XSCALE_FMT_STRETCH) + nStatus = XFrame_Stretch(pFrameOut, pFrameIn, pParams); + else if (pParams->scaleFmt == XSCALE_FMT_ASPECT) + nStatus = XFrame_Aspect(pFrameOut, pFrameIn, pParams); + + return nStatus; +} + +AVFrame* XFrame_NewResample(AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Allocate AVFrame structure */ + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Resample AVFrame */ + int nStatus = XFrame_Resample(pFrameOut, pFrameIn, pParams); + XASSERT_CALL((nStatus > 0), av_frame_free, &pFrameOut, NULL); + + return pFrameOut; +} + +AVFrame* XFrame_NewStretch(AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Allocate AVFrame structure */ + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Stretch AVFrame */ + int nStatus = XFrame_Stretch(pFrameOut, pFrameIn, pParams); + XASSERT_CALL((nStatus > 0), av_frame_free, &pFrameOut, NULL); + + return pFrameOut; +} + +AVFrame* XFrame_NewAspect(AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Allocate AVFrame structure */ + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Scale AVFrame */ + int nStatus = XFrame_Aspect(pFrameOut, pFrameIn, pParams); + XASSERT_CALL((nStatus > 0), av_frame_free, &pFrameOut, NULL); + + return pFrameOut; +} + +AVFrame* XFrame_NewScale(AVFrame *pFrameIn, xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Allocate AVFrame structure */ + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Scale AVFrame */ + int nStatus = XFrame_Scale(pFrameOut, pFrameIn, pParams); + XASSERT_CALL((nStatus > 0), av_frame_free, &pFrameOut, NULL); + + return pFrameOut; +} + +AVFrame* XFrame_NewBlackYUV(xframe_params_t *pParams) +{ + XASSERT_RET(pParams, NULL); + xstatus_t *pStatus = &pParams->status; + + /* Allocate AVFrame structure */ + AVFrame *pFrameOut = av_frame_alloc(); + XASSERT(pFrameOut, XStat_ErrPtr(pStatus, "Failed to alloc AVFrame")); + + /* Initialize black YUV420 buffer */ + int nStatus = XFrame_BlackYUV(pFrameOut, pParams); + XASSERT_CALL((nStatus > 0), av_frame_free, &pFrameOut, NULL); + + return pFrameOut; +} diff --git a/src/frame.h b/src/frame.h new file mode 100644 index 0000000..28ea92d --- /dev/null +++ b/src/frame.h @@ -0,0 +1,74 @@ +/*! + * @file libxmedia/src/frame.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio frame + * functionality based on the FFMPEG API and libraries. + */ + +#ifndef __XMEDIA_FRAME_H__ +#define __XMEDIA_FRAME_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "status.h" + +typedef enum { + XSCALE_FMT_NONE, + XSCALE_FMT_ASPECT, + XSCALE_FMT_STRETCH, +} xscale_fmt_t; + +typedef struct xframe_params_ { + /* Audio parameters */ + enum AVSampleFormat sampleFmt; + int nSampleRate; + int nChannels; + + /* Video parameters */ + enum AVPixelFormat pixFmt; + xscale_fmt_t scaleFmt; + int nWidth; + int nHeight; + + /* General parameters */ + enum AVMediaType mediaType; + xstatus_t status; + int64_t nPTS; + int nIndex; +} xframe_params_t; + +xscale_fmt_t XFrame_GetScaleFmt(const char* pFmtName); +int XFrame_GetChannelCount(AVFrame *pFrame); + +void XFrame_InitParams(xframe_params_t *pFrame, xframe_params_t *pParent); +void XFrame_InitChannels(AVFrame *pFrame, int nChannels); +void XFrame_InitFrame(AVFrame* pFrame); + +AVFrame* XFrame_FromOpus(uint8_t *pOpusBuff, size_t nSize, xframe_params_t *pParams); +AVFrame* XFrame_FromYUV(uint8_t *pYUVBuff, size_t nSize, xframe_params_t *pParams); + +XSTATUS XFrame_OverlayYUV(AVFrame *pDstFrame, AVFrame *pSrcFrame, xframe_params_t *pParams); +XSTATUS XFrame_BlackYUV(AVFrame *pFrameOut, xframe_params_t *pParams); + +XSTATUS XFrame_Resample(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams); +XSTATUS XFrame_Stretch(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams); +XSTATUS XFrame_Aspect(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams); +XSTATUS XFrame_Scale(AVFrame *pFrameOut, AVFrame *pFrameIn, xframe_params_t *pParams); + +AVFrame* XFrame_NewResample(AVFrame *pFrameIn, xframe_params_t *pParams); +AVFrame* XFrame_NewStretch(AVFrame *pFrameIn, xframe_params_t *pParams); +AVFrame* XFrame_NewAspect(AVFrame *pFrameIn, xframe_params_t *pParams); +AVFrame* XFrame_NewScale(AVFrame *pFrameIn, xframe_params_t *pParams); +AVFrame* XFrame_NewBlackYUV(xframe_params_t *pParams); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_FRAME_H__ */ diff --git a/src/meta.c b/src/meta.c new file mode 100644 index 0000000..8984efd --- /dev/null +++ b/src/meta.c @@ -0,0 +1,165 @@ +/*! + * @file libxmedia/src/meta.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio metadata + * functionality based on the FFMPEG API and libraries. + */ + +#include "meta.h" + +XSTATUS XChapter_RescaleTiming(AVChapter *pChapter, size_t nStartSec, size_t nEndSec) +{ + XASSERT(pChapter, XSTDINV); + pChapter->start = av_rescale_q(nStartSec * AV_TIME_BASE, AV_TIME_BASE_Q, pChapter->time_base); + pChapter->end = av_rescale_q(nEndSec * AV_TIME_BASE, AV_TIME_BASE_Q, pChapter->time_base) - 1; + return XSTDOK; +} + +void XChapter_Destroy(AVChapter *pChapter) +{ + XASSERT_VOID_RET(pChapter); + av_dict_free(&pChapter->metadata); + pChapter->metadata = NULL; + av_freep(&pChapter); +} + +AVChapter* XChapter_Create(uint32_t nID, AVRational timeBase, int64_t nStart, int64_t nEnd, const char *pTitle) +{ + AVChapter *pChapter = av_mallocz(sizeof(AVChapter)); + XASSERT(pChapter, NULL); + + pChapter->time_base = timeBase; + pChapter->start = nStart; + pChapter->end = nEnd; + pChapter->id = nID; + pChapter->metadata = NULL; + + if (pTitle != NULL) + av_dict_set(&pChapter->metadata, "title", pTitle, 0); + + return pChapter; +} + +AVChapter* XChapter_FromTime(uint32_t nID, const char *pStartTime, const char *pEndTime, const char *pTitle) +{ + int nStartHours = 0, nStartMinutes = 0, nStartSeconds = 0; + int nEndHours = 0, nEndMinutes = 0, nEndSeconds = 0; + + int nStartCount = sscanf(pStartTime, "%02d:%02d:%02d", &nStartHours, &nStartMinutes, &nStartSeconds); + int nEndCount = sscanf(pStartTime, "%02d:%02d:%02d", &nEndHours, &nEndMinutes, &nEndSeconds); + XASSERT((nStartCount == 3 && nEndCount == 3), NULL); + + AVChapter* pChapter = XChapter_Create(nID, XMETA_TIMEBASE, 0, 0, pTitle); + XASSERT(pChapter, NULL); + + int nStartSec = nStartHours * 3600 + nStartMinutes * 60 + nStartSeconds; + int nEndSec = nEndHours * 3600 + nEndMinutes * 60 + nEndSeconds; + XChapter_RescaleTiming(pChapter, nStartSec, nEndSec); + + return pChapter; +} + +AVChapter* XChapter_FromSeconds(uint32_t nID, size_t nStartSec, size_t nEndSec, const char *pTitle) +{ + AVChapter* pChapter = XChapter_Create(nID, XMETA_TIMEBASE, 0, 0, pTitle); + XASSERT((XChapter_RescaleTiming(pChapter, nStartSec, nEndSec) > 0), NULL); + return pChapter; +} + +void XMeta_ClearCb(xarray_data_t *pArrData) +{ + XASSERT_VOID_RET((pArrData && pArrData->pData)); + XChapter_Destroy((AVChapter*)pArrData->pData); +} + +void XMeta_Init(xmeta_t *pMeta) +{ + XASSERT_VOID(pMeta); + + XStat_Init(&pMeta->status, XSTDNON, NULL, NULL); + XArray_Init(&pMeta->chapters, 0, XFALSE); + + pMeta->chapters.clearCb = XMeta_ClearCb; + pMeta->pData = NULL; +} + +void XMeta_Clear(xmeta_t *pMeta) +{ + XASSERT_VOID(pMeta); + XArray_Destroy(&pMeta->chapters); + + if (pMeta->pData != NULL) + { + av_dict_free(&pMeta->pData); + pMeta->pData = NULL; + } +} + +XSTATUS XMeta_AddField(xmeta_t *pMeta, const char *pName, const char *pData) +{ + XASSERT(pMeta, XSTDINV); + xstatus_t *pStatus = &pMeta->status; + + XASSERT(pName, XStat_ErrCb(pStatus, "Invalid name argument")); + XASSERT(pData, XStat_ErrCb(pStatus, "Invalid data argument")); + + pStatus->nAVStatus = av_dict_set(&pMeta->pData, pName, pData, 0); + XASSERT((pStatus->nAVStatus > 0), XStat_ErrCb(pStatus, "Failed to add meta field")); + + return XSTDOK; +} + +XSTATUS XMeta_AddChapter(xmeta_t *pMeta, AVRational timeBase, int64_t nStart, int64_t nEnd, const char *pTitle) +{ + XASSERT(pMeta, XSTDINV); + xstatus_t *pStatus = &pMeta->status; + const char *pName = pTitle ? pTitle : "NULL"; + + uint32_t nID = (uint32_t)XArray_Used(&pMeta->chapters) + (uint32_t)1; + AVChapter *pChapter = XChapter_Create(nID, timeBase, nStart, nEnd, pTitle); + XASSERT(pChapter, XStat_ErrCb(pStatus, "Failed to create chapter: title(%s)", pName)); + + XSTATUS nStatus = XArray_AddData(&pMeta->chapters, pChapter, 0); + XASSERT_CALL((nStatus > 0), XChapter_Destroy, pChapter, + XStat_ErrCb(pStatus, "Failed to store chapter: title(%s)", pName)); + + return XSTDOK; +} + + +XSTATUS XMeta_AddChapterTime(xmeta_t *pMeta, const char *pStartTime, const char *pEndTime, const char *pTitle) +{ + XASSERT(pMeta, XSTDINV); + xstatus_t *pStatus = &pMeta->status; + const char *pName = pTitle ? pTitle : "NULL"; + + uint32_t nID = (uint32_t)XArray_Used(&pMeta->chapters) + (uint32_t)1; + AVChapter *pChapter = XChapter_FromTime(nID, pStartTime, pEndTime, pTitle); + XASSERT(pChapter, XStat_ErrCb(pStatus, "Failed to create chapter: title(%s)", pName)); + + XSTATUS nStatus = XArray_AddData(&pMeta->chapters, pChapter, 0); + XASSERT_CALL((nStatus > 0), XChapter_Destroy, pChapter, + XStat_ErrCb(pStatus, "Failed to store chapter: title(%s)", pName)); + + return XSTDOK; +} + +XSTATUS XMeta_AddChapterSec(xmeta_t *pMeta, size_t nStartSec, size_t nEndSec, const char *pTitle) +{ + XASSERT(pMeta, XSTDINV); + xstatus_t *pStatus = &pMeta->status; + const char *pName = pTitle ? pTitle : "NULL"; + + uint32_t nID = (uint32_t)XArray_Used(&pMeta->chapters) + (uint32_t)1; + AVChapter *pChapter = XChapter_FromSeconds(nID, nStartSec, nEndSec, pTitle); + XASSERT(pChapter, XStat_ErrCb(pStatus, "Failed to create chapter: title(%s)", pName)); + + XSTATUS nStatus = XArray_AddData(&pMeta->chapters, pChapter, 0); + XASSERT_CALL((nStatus > 0), XChapter_Destroy, pChapter, + XStat_ErrCb(pStatus, "Failed to store chapter: title(%s)", pName)); + + return XSTDOK; +} \ No newline at end of file diff --git a/src/meta.h b/src/meta.h new file mode 100644 index 0000000..bb12c08 --- /dev/null +++ b/src/meta.h @@ -0,0 +1,47 @@ +/*! + * @file libxmedia/src/meta.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio metadata + * functionality based on the FFMPEG API and libraries. + */ + +#ifndef __XMEDIA_META_H__ +#define __XMEDIA_META_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "status.h" + +/* Default timebase used in metadata */ +#define XMETA_TIMEBASE (AVRational){1,10000} + +typedef struct xmeta_ { + AVDictionary *pData; + xarray_t chapters; + xstatus_t status; +} xmeta_t; + +XSTATUS XChapter_RescaleTiming(AVChapter *pChapter, size_t nStartSec, size_t nDuration); +AVChapter* XChapter_FromSeconds(uint32_t nID, size_t nStartSec, size_t nEndSec, const char *pTitle); +AVChapter* XChapter_FromTime(uint32_t nID, const char *pStartTime, const char *pEndTime, const char *pTitle); +AVChapter* XChapter_Create(uint32_t nID, AVRational timeBase, int64_t nStart, int64_t nEnd, const char *pTitle); +void XChapter_Destroy(AVChapter *pChapter); + +void XMeta_Init(xmeta_t *pMeta); +void XMeta_Clear(xmeta_t *pMeta); +XSTATUS XMeta_AddField(xmeta_t *pMeta, const char *pName, const char *pData); +XSTATUS XMeta_AddChapter(xmeta_t *pMeta, AVRational timeBase, int64_t nStart, int64_t nEnd, const char *pTitle); +XSTATUS XMeta_AddChapterTime(xmeta_t *pMeta, const char *pStartTime, const char *pEndTime, const char *pTitle); +XSTATUS XMeta_AddChapterSec(xmeta_t *pMeta, size_t nStartSec, size_t nEndSec, const char *pTitle); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_META_H__ */ diff --git a/src/mpegts.c b/src/mpegts.c new file mode 100644 index 0000000..8346c81 --- /dev/null +++ b/src/mpegts.c @@ -0,0 +1,510 @@ +/*! + * @file libxmedia/src/mpegts.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of MPEG-TS packet parser functionality + */ + +#include "stdinc.h" +#include "mpegts.h" + +void XBitParser_Init(xbit_parser_t *pParser, uint8_t *pData, size_t nSize) +{ + pParser->pData = pData; + pParser->nSize = nSize; + pParser->nMask = 0x80; + pParser->nOffset = 0; + pParser->nError = 0; + pParser->nData = 0; +} + +uint32_t XBitParser_NextBit(xbit_parser_t *pParser) +{ + if (pParser->nMask != 0x01) + { + pParser->nMask >>= 1; + return pParser->nOffset; + } + + pParser->nMask = 0x80; + pParser->nOffset++; + return pParser->nOffset; +} + +uint8_t XBitParser_ReadBit(xbit_parser_t *pParser) +{ + uint8_t nRead = !!(pParser->nMask & pParser->pData[pParser->nOffset]); + if (XBitParser_NextBit(pParser) >= pParser->nSize) pParser->nError = 1; + return nRead; +} + +uint64_t XBitParser_ReadBits(xbit_parser_t *pParser, uint8_t nBits) +{ + uint64_t nReadBits = 0; + while(nBits-- && !pParser->nError) + { + nReadBits <<= 1; + nReadBits |= XBitParser_ReadBit(pParser); + } + return nReadBits; +} + +uint64_t XBitParser_WriteBit(xbit_parser_t *pParser, uint64_t nData, uint64_t nMask) +{ + if (nData & nMask) pParser->pData[pParser->nOffset] |= pParser->nMask; + else pParser->pData[pParser->nOffset] &= ~pParser->nMask; + + XBitParser_NextBit(pParser); + return nMask >> 1; +} + +void XBitParser_WriteBits(xbit_parser_t *pParser, uint8_t nBits, uint64_t nData) +{ + uint64_t nWriteMask = (uint64_t)(((uint64_t)1) << (uint64_t)(nBits - 1)); + while(nBits--) nWriteMask = XBitParser_WriteBit(pParser, nData, nWriteMask); +} + +int XTSParser_ParseHeader(xbit_parser_t *pParser, xts_packet_header_t *pHdr) +{ + pHdr->sync_byte = (uint8_t)XBitParser_ReadBits(pParser, 8); + pHdr->transport_error_indicator = (uint8_t)XBitParser_ReadBits(pParser, 1); + pHdr->payload_unit_start_indicator = (uint8_t)XBitParser_ReadBits(pParser, 1); + pHdr->transport_priority = (uint8_t)XBitParser_ReadBits(pParser, 1); + pHdr->PID = (uint16_t)XBitParser_ReadBits(pParser, 13); + pHdr->transports_crambling_control = (uint8_t)XBitParser_ReadBits(pParser, 2); + pHdr->adaptation_field_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pHdr->payload_data_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pHdr->continuty_counter = (uint8_t)XBitParser_ReadBits(pParser, 4); + return !pParser->nError; +} + +int XTSParser_ParseAdaptationField(xbit_parser_t *pParser, xadaptation_field_t *pField) +{ + pField->adaptation_field_length = (uint8_t)XBitParser_ReadBits(pParser, 8); + if (!pField->adaptation_field_length) return 0; + + pField->discontinuity_indicator = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->random_access_indicator = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->elementary_stream_priority_indicator = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->PCR_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->OPCR_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->splicing_point_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->transport_private_data_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->adaptation_field_extension_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + + if (pField->PCR_flag) pField->PCR = XBitParser_ReadBits(pParser, 48);; + if (pField->OPCR_flag) pField->OPCR = XBitParser_ReadBits(pParser, 48); + if (pField->splicing_point_flag) pField->splice_countdown = (uint8_t)XBitParser_ReadBits(pParser, 8); + + if (pField->transport_private_data_flag && !pParser->nError) + { + pField->transport_private_data_length = (uint8_t)XBitParser_ReadBits(pParser, 8); + pField->transport_private_data = &pParser->pData[pParser->nOffset]; + XBitParser_ReadBits(pParser, 8 * pField->transport_private_data_length); + } + + if (pField->adaptation_field_extension_flag) + { + pField->adaptation_extension_length = (uint8_t)XBitParser_ReadBits(pParser, 8); + pField->legal_time_window = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->piecewise_rate_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->seamless_splice_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->reserved = (uint8_t)XBitParser_ReadBits(pParser, 5); + + if (pField->legal_time_window) + { + pField->LTW_valid_flag = (uint8_t)XBitParser_ReadBits(pParser, 1); + pField->LTW_offset = (uint16_t)XBitParser_ReadBits(pParser, 15); + } + + if (pField->piecewise_rate_flag) + { + pField->piecewise_reserved = (uint8_t)XBitParser_ReadBits(pParser, 2); + pField->piecewise_rate = (uint32_t)XBitParser_ReadBits(pParser, 22); + } + + if (pField->seamless_splice_flag) + { + pField->splice_type = (uint8_t)XBitParser_ReadBits(pParser, 4); + pField->DTS_next_access_unit = XBitParser_ReadBits(pParser, 36); + } + } + + return !pParser->nError; +} + +int XTSParser_Parse(xts_packet_t *pTS, uint8_t *pData, uint32_t nSize) +{ + if (pData == NULL || nSize < XTS_PACKET_SIZE) return XSTDNON; + xadaptation_field_t *pField = &pTS->header.adaptationField; + pTS->payload_data = NULL; + pTS->payload_size = 0; + + xbit_parser_t parser; + XBitParser_Init(&parser, pData, nSize); + + XTSParser_ParseHeader(&parser, &pTS->header); + if (pTS->header.sync_byte != 0x47) return XSTDERR; + + if (pTS->header.adaptation_field_flag) + XTSParser_ParseAdaptationField(&parser, pField); + + if (pTS->header.payload_data_flag) + { + if (pTS->header.adaptation_field_flag) + { + int nOffset = pField->adaptation_field_length + 5; + if (nOffset < XTS_PACKET_SIZE) + { + pTS->payload_size = XTS_PACKET_SIZE - nOffset; + pTS->payload_data = &pData[nOffset]; + } + } + else + { + pTS->payload_size = XTS_PACKET_SIZE - 4; + pTS->payload_data = &pData[4]; + } + } + + return !parser.nError; +} + +int XTSParser_ParsePAT(xpat_t *pPat, uint8_t *pData, uint32_t nSize) +{ + if (pData == NULL || nSize < 8) return 0; + unsigned int i; + + xbit_parser_t parser; + XBitParser_Init(&parser, pData, nSize); + + pPat->pointer_field = (uint8_t)XBitParser_ReadBits(&parser, 8); + XBitParser_ReadBits(&parser, pPat->pointer_field * 8); + pPat->table_id = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPat->section_syntax_indicator = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPat->private_bit = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPat->reserved_bits = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPat->section_length = (uint16_t)XBitParser_ReadBits(&parser, 12); + pPat->transport_stream_id = (uint16_t)XBitParser_ReadBits(&parser, 16); + pPat->reserved = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPat->version_number = (uint8_t)XBitParser_ReadBits(&parser, 5); + pPat->current_next_indicator = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPat->section_number = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPat->last_section_number = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPat->programs = pPat->section_length/4 - 2; + + for (i = 0; i < pPat->programs && !parser.nError; i++) + { + if (i >= XTSPAT_TABLE_MAX) return 0; + xpat_table_t *pTable = &pPat->patTable[i]; + + pTable->program_number = (uint16_t)XBitParser_ReadBits(&parser, 16); + XBitParser_ReadBits(&parser, 3); + + if (pTable->program_number == 0) + pTable->network_PID = (uint16_t)XBitParser_ReadBits(&parser, 13); + else + pTable->program_map_PID = (uint16_t)XBitParser_ReadBits(&parser, 13); + } + + pPat->CRC_32 = (uint32_t)XBitParser_ReadBits(&parser, 32); + return !parser.nError; +} + +int XTSParser_ParsePMT(xpmt_t *pPmt, uint8_t *pData, uint32_t nSize) +{ + if (pData == NULL || nSize < 8) return 0; + uint32_t nRead, nDiff; + nRead = nDiff = 0; + + xbit_parser_t parser; + XBitParser_Init(&parser, pData, nSize); + + pPmt->pointer_field = (uint8_t)XBitParser_ReadBits(&parser, 8); + XBitParser_ReadBits(&parser, pPmt->pointer_field * 8); + pPmt->table_id = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPmt->section_syntax_indicator = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPmt->private_bit = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPmt->reserved_bits = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPmt->section_length = (uint16_t)XBitParser_ReadBits(&parser, 12); + + uint32_t nCurrentSize = (uint32_t)parser.nSize - parser.nOffset; + if (pPmt->section_length > nCurrentSize) return 0; + nDiff = nCurrentSize - pPmt->section_length; + + pPmt->program_number = (uint16_t)XBitParser_ReadBits(&parser, 16); + pPmt->reserved_2 = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPmt->version_number = (uint8_t)XBitParser_ReadBits(&parser, 5); + pPmt->current_next_indicator = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPmt->section_number = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPmt->last_section_number = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPmt->reserved_3 = (uint8_t)XBitParser_ReadBits(&parser, 3); + pPmt->PCR_PID = (uint16_t)XBitParser_ReadBits(&parser, 13); + pPmt->reserved_4 = (uint8_t)XBitParser_ReadBits(&parser, 4); + pPmt->program_info_length = (uint16_t)XBitParser_ReadBits(&parser, 12); + + while (nRead < pPmt->program_info_length) + { + pPmt->desc[pPmt->desc_count].descriptor_tag = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPmt->desc[pPmt->desc_count].descriptor_length = (uint8_t)XBitParser_ReadBits(&parser, 8); + + uint8_t nDescSize = pPmt->desc[pPmt->desc_count].descriptor_length; + uint8_t *pDstData = pPmt->desc[pPmt->desc_count].data; + uint8_t *pSrcData = &parser.pData[parser.nOffset]; + memcpy(pDstData, pSrcData, nDescSize); + + XBitParser_ReadBits(&parser, nDescSize * 8); + nRead += (nDescSize + 2); + pPmt->desc_count++; + } + + while (((parser.nSize - parser.nOffset) - nDiff) > 4) + { + xpmt_stream_t *pStream = &pPmt->streams[pPmt->stream_count]; + pStream->stream_type = (uint8_t)XBitParser_ReadBits(&parser, 8); + XBitParser_ReadBits(&parser, 3); + pStream->elementary_PID = (uint16_t)XBitParser_ReadBits(&parser, 13); + XBitParser_ReadBits(&parser, 4); + pStream->ES_info_length = (uint16_t)XBitParser_ReadBits(&parser, 12); + + int ES_info_length = pStream->ES_info_length; + while(ES_info_length > 0) + { + xpmt_desc_t *pDesc = &pStream->desc[pStream->desc_count]; + pDesc->descriptor_tag = (uint8_t)XBitParser_ReadBits(&parser, 8); + ES_info_length--; + if (ES_info_length <= 0) return 0; + pDesc->descriptor_length = (uint8_t)XBitParser_ReadBits(&parser, 8); + ES_info_length--; + if (ES_info_length < pDesc->descriptor_length) return 0; + + memcpy(pDesc->data, &parser.pData[parser.nOffset], pDesc->descriptor_length); + ES_info_length -= pDesc->descriptor_length; + XBitParser_ReadBits(&parser, pDesc->descriptor_length * 8); + pStream->desc_count++; + } + + pPmt->stream_count++; + } + + return !parser.nError; +} + +int XTSParser_ParsePES(xpes_packet_t *pPES, uint8_t* pData, int nSize) +{ + if (pData == NULL || nSize <= 0) return 0; + memset(pPES, 0, sizeof(xpes_packet_t)); + + xbit_parser_t parser; + XBitParser_Init(&parser, pData, nSize); + + pPES->packet_start_code_prefix = (uint32_t)XBitParser_ReadBits(&parser, 24); + pPES->stream_id = (uint8_t)XBitParser_ReadBits(&parser, 8); + pPES->PES_packet_length = (uint16_t)XBitParser_ReadBits(&parser, 16); + + XBitParser_ReadBits(&parser, 2); + pPES->PES_scrambling_control = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPES->PES_priority = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->data_alignment_indicator = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->copyright = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->original_or_copy = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->PTS_DTS_flags = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPES->ESCR_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->ES_rate_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->DSM_trick_mode_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->additional_copy_info_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->PES_CRC_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->PES_extension_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->PES_header_data_length = (uint8_t)XBitParser_ReadBits(&parser, 8); + + uint8_t* pExtPtr = &parser.pData[parser.nOffset]; + uint64_t nBuff[3]; + unsigned int i; + + if (pPES->PTS_DTS_flags == 2) + { + nBuff[2] = nBuff[1] = nBuff[0] = 0; + XBitParser_ReadBits(&parser, 4); + nBuff[2] = (uint8_t)XBitParser_ReadBits(&parser, 3); + XBitParser_ReadBits(&parser, 1); + nBuff[1] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + nBuff[0] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + pPES->PTS = nBuff[0] | (nBuff[1] << 15) | (nBuff[2] << 30); + } + else if (pPES->PTS_DTS_flags == 3) + { + nBuff[2] = nBuff[1] = nBuff[0] = 0; + XBitParser_ReadBits(&parser, 4); + nBuff[2] = (uint8_t)XBitParser_ReadBits(&parser, 3); + XBitParser_ReadBits(&parser, 1); + nBuff[1] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + nBuff[0] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + pPES->PTS = nBuff[0] | (nBuff[1] << 15) | (nBuff[2] << 30); + + nBuff[2] = nBuff[1] = nBuff[0] = 0; + XBitParser_ReadBits(&parser, 4); + nBuff[2] = (uint8_t)XBitParser_ReadBits(&parser, 3); + XBitParser_ReadBits(&parser, 1); + nBuff[1] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + nBuff[0] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + pPES->DTS = nBuff[0] | (nBuff[1] << 15) | (nBuff[2] << 30); + } + + if (pPES->ESCR_flag == 1) + { + nBuff[2] = nBuff[1] = nBuff[0] = 0; + XBitParser_ReadBits(&parser, 2); + nBuff[2] = (uint8_t)XBitParser_ReadBits(&parser, 3); + XBitParser_ReadBits(&parser, 1); + nBuff[1] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + nBuff[0] = (uint16_t)XBitParser_ReadBits(&parser, 15); + XBitParser_ReadBits(&parser, 1); + pPES->ESCR_ext = (uint16_t)XBitParser_ReadBits(&parser, 9); + XBitParser_ReadBits(&parser, 1); + pPES->ESCR_base = nBuff[0] | (nBuff[1] << 15) | (nBuff[2] << 30); + } + + if (pPES->ES_rate_flag == 1) + { + XBitParser_ReadBits(&parser, 1); + pPES->ES_rate = (uint32_t)XBitParser_ReadBits(&parser, 22); + XBitParser_ReadBits(&parser, 1); + } + + if (pPES->DSM_trick_mode_flag == 1) + { + pPES->trick_mode_control = (uint8_t)XBitParser_ReadBits(&parser, 3); + if (pPES->trick_mode_control == 0) + { + pPES->field_id = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPES->intra_slice_refresh = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->frequency_truncation = (uint8_t)XBitParser_ReadBits(&parser, 2); + } + else if (pPES->trick_mode_control == 1) + { + pPES->rep_cntrl = (uint8_t)XBitParser_ReadBits(&parser, 5); + } + else if (pPES->trick_mode_control == 2) + { + pPES->field_id = (uint8_t)XBitParser_ReadBits(&parser, 2); + XBitParser_ReadBits(&parser, 3); + } + else if (pPES->trick_mode_control == 3) + { + pPES->field_id = (uint8_t)XBitParser_ReadBits(&parser, 2); + pPES->intra_slice_refresh = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->frequency_truncation = (uint8_t)XBitParser_ReadBits(&parser, 2); + } + else if (pPES->trick_mode_control == 4) + { + pPES->rep_cntrl = (uint8_t)XBitParser_ReadBits(&parser, 5); + } + else + { + XBitParser_ReadBits(&parser, 5); + } + } + + if (pPES->additional_copy_info_flag == 1) + { + XBitParser_ReadBits(&parser, 1); + pPES->additional_copy_info = (uint8_t)XBitParser_ReadBits(&parser, 7); + } + + if (pPES->PES_CRC_flag == 1) + { + pPES->previous_PES_packet_CRC = (uint16_t)XBitParser_ReadBits(&parser, 16); + } + + if (pPES->PES_extension_flag == 1) + { + pPES->PES_private_data_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->pack_header_field_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->program_packet_sequence_counter_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->P_STD_buffer_flag = (uint8_t)XBitParser_ReadBits(&parser, 1); + XBitParser_ReadBits(&parser, 3); + pPES->PES_extension_flag_2 = (uint8_t)XBitParser_ReadBits(&parser, 1); + + if (pPES->PES_private_data_flag == 1) + { + for (i = 0; i < 16 && !parser.nError; i++) + { + pPES->private_data[i] = (uint8_t)XBitParser_ReadBits(&parser, 8); + } + } + + if (pPES->pack_header_field_flag == 1) + { + pPES->pack_field_length = (uint8_t)XBitParser_ReadBits(&parser, 8); + for (i = 0; i < pPES->pack_field_length && !parser.nError; i++) + { + pPES->pack_field[i] = (uint8_t)XBitParser_ReadBits(&parser, 8); + } + } + + if (pPES->program_packet_sequence_counter_flag == 1) + { + XBitParser_ReadBits(&parser, 1); + pPES->program_packet_sequence_counter = (uint8_t)XBitParser_ReadBits(&parser, 7); + XBitParser_ReadBits(&parser, 1); + pPES->MPEG1_MPEG2_identifier = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->original_stuff_length = (uint8_t)XBitParser_ReadBits(&parser, 6); + } + + if (pPES->P_STD_buffer_flag == 1) + { + XBitParser_ReadBits(&parser, 2); + pPES->P_STD_buffer_scale = (uint8_t)XBitParser_ReadBits(&parser, 1); + pPES->P_STD_buffer_size = (uint16_t)XBitParser_ReadBits(&parser, 13); + } + + if (pPES->PES_extension_flag_2 == 1) + { + XBitParser_ReadBits(&parser, 1); + pPES->PES_extension_field_length = (uint8_t)XBitParser_ReadBits(&parser, 7); + for (i = 0; i < pPES->PES_extension_field_length && !parser.nError; i++) + { + pPES->PES_extension_field[i] = (uint8_t)XBitParser_ReadBits(&parser, 8); + } + } + } + + uint8_t *pOffset = &parser.pData[parser.nOffset]; + while((pOffset - pExtPtr) < pPES->PES_header_data_length) + { + XBitParser_ReadBits(&parser, 8); + if (parser.nError) return 0; + pOffset = &parser.pData[parser.nOffset]; + } + + pPES->data = &parser.pData[parser.nOffset]; + pPES->data_size = (uint32_t)parser.nSize - parser.nOffset; + if (pPES->packet_start_code_prefix == 1) return 1; + + return !parser.nError; +} + +int XTSParser_ValidatePayload(const uint8_t *pPayload, size_t nSize) +{ + if (nSize % 188) return XSTDERR; + int nBlocks = (int)nSize / 188; + int i, nOffset = 0; + + /* Check pPayload with 0x47 byte */ + for (i = 0; i < nBlocks; i++) + { + if (pPayload[nOffset] != 0x47) return XSTDNON; + else nOffset += 188; + } + + return XSTDOK; +} \ No newline at end of file diff --git a/src/mpegts.h b/src/mpegts.h new file mode 100644 index 0000000..3a6d03d --- /dev/null +++ b/src/mpegts.h @@ -0,0 +1,217 @@ +/*! + * @file libxmedia/src/mpegts.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of MPEG-TS packet parser functionality + */ + +#ifndef __XUTILS_TS_H__ +#define __XUTILS_TS_H__ + +#include +#include +#include + +#define XTSPMT_DESC_DATA_MAX 1024 +#define XTSPMT_DESCRIPTIONS_MAX 16 +#define XTSPMT_STREAMS_MAX 16 +#define XTSPAT_TABLE_MAX 64 +#define XTS_PACKET_SIZE 188 + +typedef struct XAdaptationField { + uint8_t adaptation_field_length:8; + uint8_t discontinuity_indicator:1; + uint8_t random_access_indicator:1; + uint8_t elementary_stream_priority_indicator:1; + uint8_t PCR_flag:1; + uint8_t OPCR_flag:1; + uint8_t splicing_point_flag:1; + uint8_t transport_private_data_flag:1; + uint8_t adaptation_field_extension_flag:1; + /* Optional fields */ + uint64_t PCR:48; + uint64_t OPCR:48; + uint8_t splice_countdown:8; + uint8_t transport_private_data_length:8; + uint8_t *transport_private_data; + /* Adaptation extension */ + uint8_t adaptation_extension_length:8; + uint8_t legal_time_window:1; + uint8_t piecewise_rate_flag:1; + uint8_t seamless_splice_flag:1; + uint8_t reserved:5; + uint8_t LTW_valid_flag:1; + uint16_t LTW_offset:15; + uint8_t piecewise_reserved:2; + uint32_t piecewise_rate:22; + uint8_t splice_type:4; + uint64_t DTS_next_access_unit:36; +} xadaptation_field_t; + +typedef struct xts_packet_header_t_ { + uint8_t sync_byte:8; + uint8_t transport_error_indicator:1; + uint8_t payload_unit_start_indicator:1; + uint8_t transport_priority:1; + uint16_t PID:13; + uint8_t transports_crambling_control:2; + uint8_t adaptation_field_flag:1; + uint8_t payload_data_flag:1; + uint8_t continuty_counter:4; + xadaptation_field_t adaptationField; +} xts_packet_header_t; + +typedef struct XTSParser { + xts_packet_header_t header; + uint32_t payload_size; + uint8_t *payload_data; +} xts_packet_t; + +typedef struct XPESPacket { + /* Common */ + uint32_t packet_start_code_prefix:24; + uint8_t stream_id:8; + uint16_t PES_packet_length:16; + uint8_t PES_scrambling_control:2; + uint8_t PES_priority:1; + uint8_t data_alignment_indicator:1; + uint8_t copyright:1; + uint8_t original_or_copy:1; + uint8_t PTS_DTS_flags:2; + uint8_t ESCR_flag:1; + uint8_t ES_rate_flag:1; + uint8_t DSM_trick_mode_flag:1; + uint8_t additional_copy_info_flag:1; + uint8_t PES_CRC_flag:1; + uint8_t PES_extension_flag:1; + uint8_t PES_header_data_length:1; + /* Timestamps */ + uint64_t PTS; + uint64_t DTS; + /* ESCR */ + uint64_t ESCR_base; + uint16_t ESCR_ext; + uint32_t ES_rate:22; + /* Trick control */ + uint8_t trick_mode_control:3; + uint8_t field_id:2; + uint8_t intra_slice_refresh:1; + uint8_t frequency_truncation:2; + uint8_t rep_cntrl:5; + /* Additional */ + uint8_t additional_copy_info:7; + uint16_t previous_PES_packet_CRC:16; + uint8_t PES_private_data_flag:1; + uint8_t pack_header_field_flag:1; + uint8_t program_packet_sequence_counter_flag:1; + uint8_t P_STD_buffer_flag:1; + uint8_t PES_extension_flag_2:1; + uint8_t private_data[16]; + uint8_t pack_field_length; + uint8_t pack_field[256]; + uint8_t program_packet_sequence_counter:7; + uint8_t MPEG1_MPEG2_identifier:1; + uint8_t original_stuff_length:6; + uint8_t P_STD_buffer_scale:1; + uint16_t P_STD_buffer_size:13; + uint8_t PES_extension_field_length:7; + uint8_t PES_extension_field[128]; + /* Data and size */ + uint8_t* data; + uint32_t data_size; +} xpes_packet_t; + +typedef struct XPATTable { + uint16_t program_number:16; + uint16_t network_PID:13; + uint16_t program_map_PID:13; +} xpat_table_t; + +typedef struct XPAT { + uint8_t pointer_field:8; + uint8_t table_id:8; + uint8_t section_syntax_indicator:1; + uint8_t private_bit:1; + uint8_t reserved_bits:2; + uint16_t section_length:12; + uint16_t transport_stream_id:16; + uint8_t reserved:2; + uint8_t version_number:5; + uint8_t current_next_indicator:1; + uint8_t section_number:8; + uint8_t last_section_number:8; + uint16_t programs; + uint32_t CRC_32; + xpat_table_t patTable[XTSPAT_TABLE_MAX]; +} xpat_t; + +typedef struct XPMTDesc { + uint8_t descriptor_tag:8; + uint8_t descriptor_length:8; + uint8_t data[XTSPMT_DESC_DATA_MAX]; +} xpmt_desc_t; + +typedef struct XPMTStream { + uint8_t stream_type:8; + uint16_t elementary_PID:13; + uint16_t ES_info_length:12; + uint16_t desc_count; + xpmt_desc_t desc[XTSPMT_DESCRIPTIONS_MAX]; +} xpmt_stream_t; + +typedef struct XPMT { + uint8_t pointer_field:8; + uint8_t table_id:8; + uint8_t section_syntax_indicator:1; + uint8_t private_bit:1; + uint8_t reserved_bits:2; + uint16_t section_length:12; + uint16_t program_number:16; + uint8_t reserved_2:2; + uint8_t version_number:5; + uint8_t current_next_indicator:1; + uint8_t section_number:8; + uint8_t last_section_number:8; + uint8_t reserved_3:3; + uint16_t PCR_PID:13; + uint8_t reserved_4:4; + uint16_t program_info_length:12; + uint32_t desc_count; + uint16_t stream_count; + xpmt_desc_t desc[XTSPMT_DESCRIPTIONS_MAX]; + xpmt_stream_t streams[XTSPMT_STREAMS_MAX]; + uint32_t CRC_32; +} xpmt_t; + +typedef struct XBitParser { + uint32_t nOffset; + uint64_t nData; + uint8_t *pData; + uint8_t nError; + uint8_t nMask; + size_t nSize; +} xbit_parser_t; + +#ifdef __cplusplus +extern "C" { +#endif + +void XBitParser_Init(xbit_parser_t *pParser, uint8_t *pData, size_t nSize); +void XBitParser_WriteBits(xbit_parser_t *pParser, uint8_t nBits, uint64_t nData); +uint64_t XBitParser_ReadBits(xbit_parser_t *pParser, uint8_t nBits); + +int XTSParser_ParseAdaptationField(xbit_parser_t *pParser, xadaptation_field_t *pField); +int XTSParser_ParseHeader(xbit_parser_t *pParser, xts_packet_header_t *pHdr); +int XTSParser_ParsePES(xpes_packet_t *pPES, uint8_t* pData, int nSize); +int XTSParser_ParsePAT(xpat_t *pPat, uint8_t *pData, uint32_t nSize); +int XTSParser_ParsePMT(xpmt_t *pPmt, uint8_t *pData, uint32_t nSize); +int XTSParser_Parse(xts_packet_t *pTS, uint8_t *pData, uint32_t nSize); +int XTSParser_ValidatePayload(const uint8_t *pPayload, size_t nSize); + +#ifdef __cplusplus +} +#endif + +#endif /* __XUTILS_TS_H__ */ \ No newline at end of file diff --git a/src/nalu.c b/src/nalu.c new file mode 100644 index 0000000..c9bb32a --- /dev/null +++ b/src/nalu.c @@ -0,0 +1,145 @@ +/*! + * @file libxmedia/src/nalu.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the NAL units parser. + */ + +#include "nalu.h" + +static void XNAL_UnitClearCb(xarray_data_t *pArrData) +{ + XASSERT_VOID_RET(pArrData); + free(pArrData->pData); +} + +void XNAL_InitUnit(xnal_unit_t *pUnit) +{ + pUnit->nReference = 0; + pUnit->nUnitType = 0; + pUnit->nDataPos = 0; + pUnit->nNalPos = 0; + pUnit->nSize = 0; +} + +xnal_unit_t* XNAL_AllocUnit() +{ + xnal_unit_t *pUnit = (xnal_unit_t*)malloc(sizeof(xnal_unit_t)); + XASSERT(pUnit, NULL); + XNAL_InitUnit(pUnit); + return pUnit; +} + +size_t XNAL_CheckStartCode(uint8_t *pBuffer, size_t nPos) +{ + /* Search for NAL unit start code: 0x000001 */ + if (pBuffer[nPos] == 0x00 && + pBuffer[nPos + 1] == 0x00 && + pBuffer[nPos + 2] == 0x01) + return 3; + + /* Search for NAL unit start code: 0x00000001 */ + if (pBuffer[nPos] == 0x00 && + pBuffer[nPos + 1] == 0x00 && + pBuffer[nPos + 2] == 0x00 && + pBuffer[nPos + 3] == 0x01) + return 4; + + return 0; +} + +xarray_t* XNAL_ParseUnits(uint8_t *pBuffer, size_t nSize) +{ + xarray_t *pUnits = XArray_New(XSTDNON, XFALSE); + XASSERT(pUnits, NULL); + + pUnits->clearCb = XNAL_UnitClearCb; + size_t i; + + for (i = 0; i < nSize - 4; i++) + { + size_t nStartCodeSize = XNAL_CheckStartCode(pBuffer, i); + if (nStartCodeSize) + { + xnal_unit_t* pUnit = XNAL_AllocUnit(); + if (pUnit == NULL) + { + XArray_Destroy(pUnits); + return NULL; + } + + pUnit->nNalPos = (int)i; + pUnit->nDataPos = (int)(i + nStartCodeSize); + pUnit->nUnitType = pBuffer[pUnit->nDataPos] & 0x1f; + pUnit->nReference = pBuffer[pUnit->nDataPos] & 0x20; + + if (XArray_AddData(pUnits, pUnit, XSTDNON) <= 0) + { + XArray_Destroy(pUnits); + return NULL; + } + } + } + + /* Update NAL unit sizes */ + for (i = 0; i < pUnits->nUsed; i++) + { + xnal_unit_t* pUnit = XArray_GetData(pUnits, i); + if (pUnit == NULL) continue; + + if (i == (pUnits->nUsed - 1)) + { + /* This is the last unit in the array */ + pUnit->nSize = nSize - pUnit->nDataPos; + } + else + { + xnal_unit_t* pNext = XArray_GetData(pUnits, i + 1); + pUnit->nSize = pNext ? pNext->nNalPos - pUnit->nDataPos : 0; + } + } + + return pUnits; +} + +XSTATUS XNAL_ParseH264(uint8_t *pBuffer, size_t nSize, x264_extra_t *pExtraData) +{ + xarray_t *pUnits = XNAL_ParseUnits(pBuffer, nSize); + XASSERT(pUnits, XSTDERR); + + size_t nUsed = XArray_Used(pUnits); + if (!nUsed) + { + XArray_Destroy(pUnits); + return XSTDNON; + } + + size_t i; + xbool_t bHaveExtra = XFALSE; + + for (i = 0; i < nUsed; i++) + { + xnal_unit_t* pUnit = XArray_GetData(pUnits, i); + if (pUnit == NULL) continue; + + if (pUnit->nUnitType == 7) // SPS + { + pExtraData->pSPS = &pBuffer[pUnit->nDataPos]; + pExtraData->nSPSSize = pUnit->nSize; + } + else if (pUnit->nUnitType == 8) // PPS + { + pExtraData->pPPS = &pBuffer[pUnit->nDataPos]; + pExtraData->nPPSSize = pUnit->nSize; + } + + if (pExtraData->pSPS && + pExtraData->pPPS) + bHaveExtra = XTRUE; + } + + XArray_Destroy(pUnits); + return bHaveExtra ? XSTDOK : XSTDNON; +} \ No newline at end of file diff --git a/src/nalu.h b/src/nalu.h new file mode 100644 index 0000000..33527da --- /dev/null +++ b/src/nalu.h @@ -0,0 +1,40 @@ +/*! + * @file libxmedia/src/nalu.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the NAL units parser. + */ + +#ifndef __XMEDIA_NALU_H__ +#define __XMEDIA_NALU_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "codec.h" + +typedef struct xnal_unit_ { + int nReference; + int nUnitType; + int nDataPos; + int nNalPos; + int nSize; +} xnal_unit_t; + +void XNAL_InitUnit(xnal_unit_t *pUnit); +xnal_unit_t* XNAL_AllocUnit(); + +size_t XNAL_CheckStartCode(uint8_t *pBuffer, size_t nPos); +xarray_t* XNAL_ParseUnits(uint8_t *pBuffer, size_t nSize); + +XSTATUS XNAL_ParseH264(uint8_t *pBuffer, size_t nSize, x264_extra_t *pExtraData); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_NALU_H__ */ diff --git a/src/status.c b/src/status.c new file mode 100644 index 0000000..6320ffd --- /dev/null +++ b/src/status.c @@ -0,0 +1,137 @@ +/*! + * @file libxmedia/src/status.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the status callbacks. + */ + +#include "status.h" + +#define XSTATUS_TYPE_CHECK(c, f) (((c) & (f)) == (f)) + +void XStat_Init(xstatus_t *pStatus, int nTypes, xstatus_cb_t cb, void *pUserCtx) +{ + XASSERT_VOID(pStatus); + pStatus->nTypes = nTypes; + pStatus->nAVStatus = XSTDNON; + pStatus->pUserCtx = pUserCtx; + pStatus->cb = cb; +} + +void XStat_InitFrom(xstatus_t *pStatus, xstatus_t *pParent) +{ + XASSERT_VOID(pStatus); + + if (pParent != NULL) + { + XStat_Init(pStatus, XSTDNON, NULL, NULL); + return; + } + + pStatus->nTypes = pParent->nTypes; + pStatus->nAVStatus = XSTDNON; + pStatus->pUserCtx = pParent->pUserCtx; + pStatus->cb = pParent->cb; +} + +void* XStat_ErrPtr(xstatus_t *pStatus, const char *pFmt, ...) +{ + XASSERT_RET((pStatus && pStatus->cb), NULL); + XASSERT_RET(XSTATUS_TYPE_CHECK(pStatus->nTypes, XSTATUS_ERROR), NULL); + + char sAvError[XSTR_MIN] = { "Unknown AVError" }; + if (pStatus->nAVStatus < 0) + { + sAvError[0] = XSTR_NUL; + av_make_error_string(sAvError, sizeof(sAvError), pStatus->nAVStatus); + } + + size_t nLength = 0; + char *pDest; + va_list args; + + va_start(args, pFmt); + pDest = xstracpyargs(pFmt, args, &nLength); + va_end(args); + + if (pDest == NULL) return NULL; + char sError[XSTR_MID]; + + xstrncpyf(sError, sizeof(sError), "%s (%s)", pDest, sAvError); + pStatus->cb(pStatus->pUserCtx, XSTATUS_ERROR, sError); + + free(pDest); + return NULL; +} + +XSTATUS XStat_ErrCb(xstatus_t *pStatus, const char *pFmt, ...) +{ + XASSERT_RET((pStatus && pStatus->cb), XSTDERR); + XASSERT_RET(XSTATUS_TYPE_CHECK(pStatus->nTypes, XSTATUS_ERROR), XSTDERR); + + char sAvError[XSTR_MIN] = { "Unknown AVError" }; + if (pStatus->nAVStatus < 0) + { + sAvError[0] = XSTR_NUL; + av_make_error_string(sAvError, sizeof(sAvError), pStatus->nAVStatus); + } + + size_t nLength = 0; + char *pDest; + va_list args; + + va_start(args, pFmt); + pDest = xstracpyargs(pFmt, args, &nLength); + va_end(args); + + if (pDest == NULL) return XSTDERR; + char sError[XSTR_MID]; + + xstrncpyf(sError, sizeof(sError), "%s (%s)", pDest, sAvError); + pStatus->cb(pStatus->pUserCtx, XSTATUS_ERROR, sError); + + free(pDest); + return XSTDERR; +} + +XSTATUS XStat_InfoCb(xstatus_t *pStatus, const char *pFmt, ...) +{ + XASSERT_RET((pStatus && pStatus->cb), XSTDERR); + XASSERT_RET(XSTATUS_TYPE_CHECK(pStatus->nTypes, XSTATUS_INFO), XSTDERR); + + size_t nLength = 0; + char *pStatusStr; + va_list args; + + va_start(args, pFmt); + pStatusStr = xstracpyargs(pFmt, args, &nLength); + va_end(args); + + if (pStatusStr == NULL) return XSTDERR; + pStatus->cb(pStatus->pUserCtx, XSTATUS_INFO, pStatusStr); + + free(pStatusStr); + return XSTDOK; +} + +XSTATUS XStat_DebugCb(xstatus_t *pStatus, const char *pFmt, ...) +{ + XASSERT_RET((pStatus && pStatus->cb), XSTDERR); + XASSERT_RET(XSTATUS_TYPE_CHECK(pStatus->nTypes, XSTATUS_DEBUG), XSTDERR); + + size_t nLength = 0; + char *pStatusStr; + va_list args; + + va_start(args, pFmt); + pStatusStr = xstracpyargs(pFmt, args, &nLength); + va_end(args); + + if (pStatusStr == NULL) return XSTDERR; + pStatus->cb(pStatus->pUserCtx, XSTATUS_DEBUG, pStatusStr); + + free(pStatusStr); + return XSTDOK; +} diff --git a/src/status.h b/src/status.h new file mode 100644 index 0000000..d1a73b9 --- /dev/null +++ b/src/status.h @@ -0,0 +1,47 @@ +/*! + * @file libxmedia/src/status.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the status callbacks. + */ + +#ifndef __XMEDIA_STATUS_H__ +#define __XMEDIA_STATUS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" + +typedef enum { + XSTATUS_INFO = (1 << 0), + XSTATUS_ERROR = (1 << 1), + XSTATUS_DEBUG = (1 << 2), + XSTATUS_ALL = 7 +} xstatus_type_t; + +typedef void(*xstatus_cb_t)(void *pUserCtx, xstatus_type_t nType, const char *pStatus); + +typedef struct xstatus_ { + xstatus_cb_t cb; + uint16_t nTypes; + void *pUserCtx; + int nAVStatus; +} xstatus_t; + +void XStat_Init(xstatus_t *pStatus, int nTypes, xstatus_cb_t cb, void *pUserCtx); +void XStat_InitFrom(xstatus_t *pStatus, xstatus_t *pParent); + +XSTATUS XStat_ErrCb(xstatus_t *pStatus, const char *pFmt, ...); +XSTATUS XStat_InfoCb(xstatus_t *pStatus, const char *pFmt, ...); +XSTATUS XStat_DebugCb(xstatus_t *pStatus, const char *pFmt, ...); +void* XStat_ErrPtr(xstatus_t *pStatus, const char *pFmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_STATUS_H__ */ diff --git a/src/stdinc.h b/src/stdinc.h new file mode 100644 index 0000000..8ae4ef5 --- /dev/null +++ b/src/stdinc.h @@ -0,0 +1,49 @@ +/*! + * @file libxmedia/src/stdinc.h + * + * This source is part of "libxmedia" project + * 2022-2023 Sun Dro (s.kalatoz@gmail.com) + * + * @brief Standart includes. + */ + +#ifndef __XMEDIA_STDINC_H__ +#define __XMEDIA_STDINC_H__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + If FF_API_OLD_CHANNEL_LAYOUT is defined, that means 'channel' and + 'channel_layout' members of AVCodecContext structure are deprecated + and we need to use the new AVChannelLayout structure instead. +*/ +#ifndef XCODEC_USE_NEW_CHANNEL + #if FF_API_OLD_CHANNEL_LAYOUT + #define XCODEC_USE_NEW_CHANNEL 1 + #endif +#endif + +#ifndef XCODEC_USE_NEW_FIFO + #if FF_API_FIFO_OLD_API + #define XCODEC_USE_NEW_FIFO 1 + #endif +#endif + +#endif /* __XMEDIA_STDINC_H__ */ \ No newline at end of file diff --git a/src/stream.c b/src/stream.c new file mode 100644 index 0000000..a22643d --- /dev/null +++ b/src/stream.c @@ -0,0 +1,172 @@ +/*! + * @file libxmedia/src/stream.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio stream + * functionality based on the FFMPEG API and libraries. + */ + +#include "stream.h" + +void XStream_Init(xstream_t *pStream) +{ + XASSERT_VOID_RET(pStream); + XCodec_Init(&pStream->codecInfo); + + pStream->pCodecCtx = NULL; + pStream->pAvStream = NULL; + pStream->pPacket = NULL; + pStream->pFrame = NULL; + + pStream->nSrcIndex = XSTDERR; + pStream->nDstIndex = XSTDERR; + + pStream->nPacketCount = 0; + pStream->nPacketSize = 0; + pStream->nLastPTS = 0; + pStream->nLastDTS = 0; +} + +xstream_t* XStream_New() +{ + xstream_t *pStream = (xstream_t*)malloc(sizeof(xstream_t)); + XASSERT(pStream, NULL); + + XStream_Init(pStream); + return pStream; +} + +void XStream_Destroy(xstream_t *pStream) +{ + XASSERT_VOID_RET(pStream); + XCodec_Clear(&pStream->codecInfo); + + if (pStream->pCodecCtx != NULL) + { + avcodec_free_context(&pStream->pCodecCtx); + pStream->pCodecCtx = NULL; + } + + if (pStream->pPacket != NULL) + { + av_packet_free(&pStream->pPacket); + pStream->pPacket = NULL; + } + + if (pStream->pFrame != NULL) + { + av_frame_free(&pStream->pFrame); + pStream->pFrame = NULL; + } +} + +AVPacket* XStream_GetOrCreatePacket(xstream_t *pStream) +{ + if (pStream->pPacket != NULL) + av_packet_unref(pStream->pPacket); + else pStream->pPacket = av_packet_alloc(); + + return pStream->pPacket; +} + +AVFrame* XStream_GetOrCreateFrame(xstream_t *pStream) +{ + if (pStream->pFrame != NULL) + av_frame_unref(pStream->pFrame); + else pStream->pFrame = av_frame_alloc(); + + return pStream->pFrame; +} + +void XStreams_ClearCb(xarray_data_t *pArrData) +{ + XASSERT_VOID_RET((pArrData && pArrData->pData)); + XStream_Destroy((xstream_t*)pArrData->pData); + free(pArrData->pData); +} + +xstream_t* XStreams_NewStream(xarray_t *pStreams) +{ + xstream_t *pStream = XStream_New(); + XASSERT(pStream, NULL); + + int nStatus = XArray_AddData(pStreams, pStream, XSTDNON); + XASSERT_CALL((nStatus > 0), free, pStream, NULL); + + return pStream; +} + +xstream_t* XStreams_GetByIndex(xarray_t *pStreams, int nIndex) +{ + XASSERT(pStreams, NULL); + return (xstream_t*)XArray_GetData(pStreams, nIndex); +} + +xstream_t* XStreams_GetBySrcIndex(xarray_t *pStreams, int nSrcIndex) +{ + XASSERT(pStreams, NULL); + size_t i, nCount = XArray_Used(pStreams); + + for (i = 0; i < nCount; i++) + { + xstream_t *pStream = (xstream_t*)XArray_GetData(pStreams, i); + if (pStream && pStream->nSrcIndex == nSrcIndex) return pStream; + } + + return NULL; +} + +xstream_t* XStreams_GetByDstIndex(xarray_t *pStreams, int nDstIndex) +{ + XASSERT(pStreams, NULL); + size_t i, nCount = XArray_Used(pStreams); + + for (i = 0; i < nCount; i++) + { + xstream_t *pStream = (xstream_t*)XArray_GetData(pStreams, i); + if (pStream && pStream->nDstIndex == nDstIndex) return pStream; + } + + return NULL; +} + +const xcodec_t* XStream_GetCodecInfo(xstream_t *pStream) +{ + XASSERT(pStream, NULL); + return &pStream->codecInfo; +} + +XSTATUS XStream_CopyCodecInfo(xstream_t *pStream, xcodec_t *pInfo) +{ + XASSERT((pStream && pInfo), XSTDINV); + return XCodec_Copy(pInfo, &pStream->codecInfo); +} + +XSTATUS XStream_FlushBuffers(xstream_t *pStream) +{ + XASSERT((pStream && pStream->pCodecCtx), XSTDINV); + XASSERT(pStream->bCodecOpen, XSTDNON); + + avcodec_flush_buffers(pStream->pCodecCtx); + return XSTDOK; +} + +size_t XStreams_GetCount(xarray_t *pStreams) +{ + XASSERT(pStreams, XSTDNON); + return XArray_Used(pStreams); +} + +void XStreams_Init(xarray_t *pStreams) +{ + XArray_Init(pStreams, XSTDNON, XFALSE); + pStreams->clearCb = XStreams_ClearCb; +} + +void XStreams_Destroy(xarray_t *pStreams) +{ + XASSERT_VOID_RET(pStreams); + XArray_Destroy(pStreams); +} diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 0000000..133dbaf --- /dev/null +++ b/src/stream.h @@ -0,0 +1,64 @@ +/*! + * @file libxmedia/src/stream.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Implementation of the video/audio stream + * functionality based on the FFMPEG API and libraries. + */ + +#ifndef __XMEDIA_STREAM_H__ +#define __XMEDIA_STREAM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdinc.h" +#include "codec.h" + +typedef struct xstream_ { + xcodec_t codecInfo; + xbool_t bCodecOpen; + + AVCodecContext* pCodecCtx; + AVStream* pAvStream; + AVPacket* pPacket; + AVFrame* pFrame; + + uint64_t nPacketCount; + size_t nPacketSize; + int64_t nLastPTS; + int64_t nLastDTS; + + int nSrcIndex; + int nDstIndex; +} xstream_t; + +xstream_t* XStream_New(); +void XStream_Destroy(xstream_t *pStream); +void XStream_Init(xstream_t *pStream); + +AVPacket* XStream_GetOrCreatePacket(xstream_t *pStream); +AVFrame* XStream_GetOrCreateFrame(xstream_t *pStream); + +const xcodec_t* XStream_GetCodecInfo(xstream_t *pStream); +XSTATUS XStream_CopyCodecInfo(xstream_t *pStream, xcodec_t *pInfo); +XSTATUS XStream_FlushBuffers(xstream_t *pStream); + +void XStreams_Init(xarray_t *pStreams); +void XStreams_Destroy(xarray_t *pStreams); + +xstream_t* XStreams_NewStream(xarray_t *pStreams); +size_t XStreams_GetCount(xarray_t *pStreams); + +xstream_t* XStreams_GetByIndex(xarray_t *pStreams, int nIndex); +xstream_t* XStreams_GetBySrcIndex(xarray_t *pStreams, int nSrcIndex); +xstream_t* XStreams_GetByDstIndex(xarray_t *pStreams, int nDstIndex); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_STREAM_H__ */ diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..76a6e75 --- /dev/null +++ b/src/version.c @@ -0,0 +1,50 @@ +/*! + * @file libxmedia/src/version.c + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Project version and build number. + */ + +#include "stdinc.h" +#include "version.h" + +static XATOMIC g_nHaveVerShort = 0; +static XATOMIC g_nHaveVerLong = 0; +static char g_xMediaVerShort[128]; +static char g_xMediaVerLong[256]; + +size_t XMedia_GetVersion(char *pDstStr, size_t nSize, xbool_t bShort) +{ + if (bShort) + { + return xstrncpyf(pDstStr, sizeof(pDstStr), "%d.%d.%d", + XMEDIA_VERSION_MAX, XMEDIA_VERSION_MIN, XMEDIA_BUILD_NUMBER); + } + + return xstrncpyf(pDstStr, sizeof(pDstStr), "%d.%d build %d (%s)", + XMEDIA_VERSION_MAX, XMEDIA_VERSION_MIN, XMEDIA_BUILD_NUMBER, __DATE__); +} + +const char* XMedia_Version(void) +{ + if (!XSYNC_ATOMIC_GET(&g_nHaveVerLong)) + { + XMedia_GetVersion(g_xMediaVerLong, sizeof(g_xMediaVerLong), XFALSE); + XSYNC_ATOMIC_SET(&g_nHaveVerLong, XTRUE); + } + + return g_xMediaVerLong; +} + +const char* XMedia_VersionShort(void) +{ + if (!XSYNC_ATOMIC_GET(&g_nHaveVerShort)) + { + XMedia_GetVersion(g_xMediaVerLong, sizeof(g_xMediaVerLong), XTRUE); + XSYNC_ATOMIC_SET(&g_nHaveVerShort, XTRUE); + } + + return g_xMediaVerShort; +} diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..73620a1 --- /dev/null +++ b/src/version.h @@ -0,0 +1,31 @@ +/*! + * @file libxmedia/src/version.h + * + * This source is part of "libxmedia" project + * 2022-2023 (c) Sun Dro (s.kalatoz@gmail.com) + * + * @brief Project version and build number. + */ + +#ifndef __XMEDIA_XLIBVER_H__ +#define __XMEDIA_XLIBVER_H__ + +#include "stdinc.h" + +#define XMEDIA_VERSION_MAX 1 +#define XMEDIA_VERSION_MIN 2 +#define XMEDIA_BUILD_NUMBER 13 + +#ifdef __cplusplus +extern "C" { +#endif + +size_t XMedia_GetVersion(char *pDstStr, size_t nSize, xbool_t bShort); +const char* XMedia_VersionShort(void); +const char* XMedia_Version(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __XMEDIA_XLIBVER_H__ */