root/src/add-ons/media/plugins/ffmpeg/Utilities.h
/*
 * Copyright 2009, Stephan Aßmus <superstippi@gmx.de>
 * Copyright 2014, Colin Günther <coling@gmx.de>
 * Copyright 2018, Dario Casalinuovo
 * All rights reserved. Distributed under the terms of the GNU L-GPL license.
 */
#ifndef UTILITIES_H
#define UTILITIES_H


/*! \brief This file contains functions to convert and calculate values from
                FFmpeg to Media Kit and vice versa.
*/


#include <assert.h>
#include <stdio.h>

#include <GraphicsDefs.h>

extern "C" {
        #include "avcodec.h"
}


/*! \brief Structure used for passing AVPacket metadata through media_header::user_data. */
struct avpacket_user_data {
    int64_t pts;
    int64_t dts;
    int stream_index;
    int flags;
    int64_t duration;
    int64_t pos;
};

#define AVPACKET_USER_DATA_TYPE 'ffav'


/*! \brief Converts FFmpeg notation of video aspect ratio into the Media Kits
                notation.

        \see ConvertVideoAspectWidthAndHeightToAVCodecContext() for converting in
                the other direction.

        \param contextIn An AVCodeContext structure of FFmpeg containing the values
                needed to calculate the Media Kit video aspect ratio.
                The following fields are used for the calculation:
                        - AVCodecContext.sample_aspect_ratio.num (optional)
                        - AVCodecContext.sample_aspect_ratio.den (optional)
                        - AVCodecContext.width (must)
                        - AVCodecContext.height (must)
        \param pixelWidthAspectOut On return contains Media Kits notation of the
                video aspect ratio width. E.g. 16:9 -> 16 is returned here
        \param pixelHeightAspectOut On return contains Media Kits notation of the
                video aspect ratio height. E.g. 16:9 -> 9 is returned here
*/
inline void
ConvertAVCodecContextToVideoAspectWidthAndHeight(AVCodecContext& contextIn,
        uint16& pixelWidthAspectOut, uint16& pixelHeightAspectOut)
{
        if (contextIn.width <= 0 || contextIn.height <= 0) {
                fprintf(stderr, "Cannot compute video aspect ratio correctly\n");
                pixelWidthAspectOut = 1;
                pixelHeightAspectOut = 1;
                return;
        }

        assert(contextIn.sample_aspect_ratio.num >= 0);

        AVRational pixelAspectRatio;

        if (contextIn.sample_aspect_ratio.num == 0
                || contextIn.sample_aspect_ratio.den == 0) {
                // AVCodecContext doesn't contain a video aspect ratio, so calculate it
                // ourselve based solely on the video dimensions
                av_reduce(&pixelAspectRatio.num, &pixelAspectRatio.den, contextIn.width,
                        contextIn.height, 1024 * 1024);

                pixelWidthAspectOut = static_cast<int16>(pixelAspectRatio.num);
                pixelHeightAspectOut = static_cast<int16>(pixelAspectRatio.den);
                return;
        }

        // AVCodecContext contains a video aspect ratio, so use it
        av_reduce(&pixelAspectRatio.num, &pixelAspectRatio.den,
                contextIn.width * contextIn.sample_aspect_ratio.num,
                contextIn.height * contextIn.sample_aspect_ratio.den,
                1024 * 1024);

        pixelWidthAspectOut = static_cast<int16>(pixelAspectRatio.num);
        pixelHeightAspectOut = static_cast<int16>(pixelAspectRatio.den);
}


inline void
ConvertAVCodecParametersToVideoAspectWidthAndHeight(AVCodecParameters& parametersIn,
        uint16& pixelWidthAspectOut, uint16& pixelHeightAspectOut)
{
        if (parametersIn.width <= 0 || parametersIn.height <= 0) {
                fprintf(stderr, "Cannot compute video aspect ratio correctly\n");
                pixelWidthAspectOut = 1;
                pixelHeightAspectOut = 1;
                return;
        }

        assert(parametersIn.sample_aspect_ratio.num >= 0);

        AVRational pixelAspectRatio;

        if (parametersIn.sample_aspect_ratio.num == 0
                || parametersIn.sample_aspect_ratio.den == 0) {
                // AVCodecContext doesn't contain a video aspect ratio, so calculate it
                // ourselve based solely on the video dimensions
                av_reduce(&pixelAspectRatio.num, &pixelAspectRatio.den, parametersIn.width,
                        parametersIn.height, 1024 * 1024);

                pixelWidthAspectOut = static_cast<int16>(pixelAspectRatio.num);
                pixelHeightAspectOut = static_cast<int16>(pixelAspectRatio.den);
                return;
        }

        // AVCodecContext contains a video aspect ratio, so use it
        av_reduce(&pixelAspectRatio.num, &pixelAspectRatio.den,
                parametersIn.width * parametersIn.sample_aspect_ratio.num,
                parametersIn.height * parametersIn.sample_aspect_ratio.den,
                1024 * 1024);

        pixelWidthAspectOut = static_cast<int16>(pixelAspectRatio.num);
        pixelHeightAspectOut = static_cast<int16>(pixelAspectRatio.den);
}


/*!     \brief Converts the Media Kits notation of video aspect ratio into FFmpegs
                notation.

        \see ConvertAVCodecContextToVideoAspectWidthAndHeight() for converting in
                the other direction.

        \param pixelWidthAspectIn Contains Media Kits notation of the video aspect
                ratio width. E.g. 16:9 -> 16 is passed here.
        \param pixelHeightAspectIn Contains Media Kits notation of the video aspect
                ratio height. E.g. 16:9 -> 9 is passed here.
        \param contextInOut     An AVCodecContext structure of FFmpeg.
                On input must contain the following fields already initialized
                otherwise the behaviour is undefined:
                        - AVCodecContext.width (must)
                        - AVCodecContext.height (must)
                On output contains converted values in the following fields (other
                fields stay as they were on input):
                        - AVCodecContext.sample_aspect_ratio.num
                        - AVCodecContext.sample_aspect_ratio.den
*/
inline void
ConvertVideoAspectWidthAndHeightToAVCodecContext(uint16 pixelWidthAspectIn,
        uint16 pixelHeightAspectIn, AVCodecContext& contextInOut)
{
        if (contextInOut.width <= 0 || contextInOut.height <= 0) {
                fprintf(stderr, "Cannot compute video aspect ratio correctly\n");
                // We can't do anything, set the aspect ratio to 'ignore'.
                contextInOut.sample_aspect_ratio.num = 0;
                contextInOut.sample_aspect_ratio.den = 1;
                return;
        }

        assert(pixelWidthAspectIn > 0);
        assert(pixelHeightAspectIn > 0);

        AVRational pureVideoDimensionAspectRatio;
        av_reduce(&pureVideoDimensionAspectRatio.num,
                &pureVideoDimensionAspectRatio.den, contextInOut.width,
                contextInOut.height, 1024 * 1024);

        if (pureVideoDimensionAspectRatio.num == pixelWidthAspectIn
                && pureVideoDimensionAspectRatio.den == pixelHeightAspectIn) {
                // The passed Media Kit pixel aspect ratio equals the video dimension
                // aspect ratio. Set sample_aspect_ratio to "ignore".
                contextInOut.sample_aspect_ratio.num = 0;
                contextInOut.sample_aspect_ratio.den = 1;
                return;
        }

        av_reduce(&contextInOut.sample_aspect_ratio.num,
                &contextInOut.sample_aspect_ratio.den,
                contextInOut.height * pixelWidthAspectIn,
                contextInOut.width * pixelHeightAspectIn,
                1024 * 1024);
}


/*! \brief Calculates bytes per row for a video frame.

        \param colorSpace The Media Kit color space the video frame uses.
        \param videoWidth The width of the video frame.

        \returns bytes per video frame row
        \returns Zero, when bytes per video frame cannot be calculated.
*/
inline uint32
CalculateBytesPerRowWithColorSpaceAndVideoWidth(color_space colorSpace, int videoWidth)
{
        assert(videoWidth >= 0);

        const uint32 kBytesPerRowUnknown = 0;
        size_t pixelChunk;
        size_t rowAlignment;
        size_t pixelsPerChunk;

        if (get_pixel_size_for(colorSpace, &pixelChunk, &rowAlignment, &pixelsPerChunk) != B_OK)
                return kBytesPerRowUnknown;

        uint32 bytesPerRow = pixelChunk * videoWidth / pixelsPerChunk;
        uint32 numberOfUnalignedBytes = bytesPerRow % rowAlignment;

        if (numberOfUnalignedBytes == 0)
                return bytesPerRow;

        uint32 numberOfBytesNeededForAlignment = rowAlignment - numberOfUnalignedBytes;
        bytesPerRow += numberOfBytesNeededForAlignment;

        return bytesPerRow;
}


/*!     \brief Converts the Media Kits notation of video frame rate to FFmpegs
        notation.

        \see ConvertAVCodecContextToVideoFrameRate() for converting in the other
                direction.

        \param frameRateIn Contains Media Kits notation of the video frame rate
                that will be converted into FFmpegs notation. Must be greater than
                zero.
        \param contextOut An AVCodecContext structure of FFmpeg.
                On output contains converted values in the following fields (other
                fields stay as they were on input):
                        - AVCodecContext.time_base.num
                        - AVCodecContext.time_base.den
                        - AVCodecContext.framerate.num
                        - AVCodecContext.framerate.den
*/
inline void
ConvertVideoFrameRateToAVCodecContext(float frameRateIn,
        AVCodecContext& contextOut)
{
        assert(frameRateIn > 0);

        contextOut.framerate = av_d2q(frameRateIn, 1024);
        contextOut.time_base = av_d2q(1.0 / frameRateIn, 1024);
}


/*!     \brief Converts the Media Kits notation of an audio sample format to
                FFmpegs notation.

        \see ConvertAVSampleFormatToRawAudioFormat() for converting in the other
                direction.

        \param rawAudioFormatIn Contains Media Kits notation of an audio sample
                format that will be converted into FFmpegs notation.
        \param sampleFormatOut On output contains FFmpegs notation of the passed
                audio sample format. Might return AV_SAMPLE_FMT_NONE if there is no
                conversion path.
*/
inline void
ConvertRawAudioFormatToAVSampleFormat(uint32 rawAudioFormatIn,
        AVSampleFormat& sampleFormatOut)
{
        switch (rawAudioFormatIn) {
                case media_raw_audio_format::B_AUDIO_FLOAT:
                        sampleFormatOut = AV_SAMPLE_FMT_FLT;
                        return;

                case media_raw_audio_format::B_AUDIO_DOUBLE:
                        sampleFormatOut = AV_SAMPLE_FMT_DBL;
                        return;

                case media_raw_audio_format::B_AUDIO_INT:
                        sampleFormatOut = AV_SAMPLE_FMT_S32;
                        return;

                case media_raw_audio_format::B_AUDIO_SHORT:
                        sampleFormatOut = AV_SAMPLE_FMT_S16;
                        return;

                case media_raw_audio_format::B_AUDIO_UCHAR:
                        sampleFormatOut = AV_SAMPLE_FMT_U8;
                        return;

                default:
                        // Silence compiler warnings about unhandled enumeration values.
                        break;
        }

        sampleFormatOut = AV_SAMPLE_FMT_NONE;
}


/*!     \brief Converts FFmpegs notation of an audio sample format to the Media
                Kits notation.

        \see ConvertAVSampleFormatToRawAudioFormat() for converting in the other
                direction.

        \param sampleFormatIn Contains FFmpegs notation of an audio sample format
                that will be converted into the Media Kits notation.
        \param rawAudioFormatOut On output contains Media Kits notation of the
                passed audio sample format. Might return 0 if there is no conversion
                path.
*/
inline void
ConvertAVSampleFormatToRawAudioFormat(AVSampleFormat sampleFormatIn,
        uint32& rawAudioFormatOut)
{
        switch (sampleFormatIn) {
                case AV_SAMPLE_FMT_FLT:
                case AV_SAMPLE_FMT_FLTP:
                        rawAudioFormatOut = media_raw_audio_format::B_AUDIO_FLOAT;
                        return;

                case AV_SAMPLE_FMT_DBL:
                case AV_SAMPLE_FMT_DBLP:
                        rawAudioFormatOut = media_raw_audio_format::B_AUDIO_DOUBLE;
                        return;

                case AV_SAMPLE_FMT_S32:
                case AV_SAMPLE_FMT_S32P:
                        rawAudioFormatOut = media_raw_audio_format::B_AUDIO_INT;
                        return;

                case AV_SAMPLE_FMT_S16:
                case AV_SAMPLE_FMT_S16P:
                        rawAudioFormatOut = media_raw_audio_format::B_AUDIO_SHORT;
                        return;

                case AV_SAMPLE_FMT_U8:
                case AV_SAMPLE_FMT_U8P:
                        rawAudioFormatOut = media_raw_audio_format::B_AUDIO_UCHAR;
                        return;

                default:
                        // Silence compiler warnings about unhandled enumeration values.
                        break;
        }

        const uint32 kBAudioNone = 0;
        rawAudioFormatOut = kBAudioNone;
}


#endif // UTILITIES_H