//
// C++ Implementation: k9avidecode
//
// Description:
//
//
// Author: Jean-Michel PETIT <k9copy@free.fr>, (C) 2007
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "config.h"
#include "k9avidecode.h"

// This is probably the incorrect revision for when CODEC_TYPE_VIDEO was removed
// Please update the comparison below if you know the exact revision that CODEC_TYPE_VIDEO was removed in!
#if LIBAVCODEC_VERSION_INT < (AV_VERSION_INT(52,72,2))
#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
#endif

#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(51,42,0)
#define AV_PIX_FMT_RGB24 PIX_FMT_RGB24
#endif

#include <tqimage.h>
#include <dlfcn.h>
#include <tdelocale.h>
#include <cstdlib>
#include "ac.h"


static int sws_flags = SWS_BICUBIC;

k9AviDecode::k9AviDecode(TQObject *parent, const char *name)
        : TQObject(parent, name) {

#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58,9,100)
    av_register_all();
#endif
    m_opened=false;

    m_FormatCtx = NULL;
    m_CodecCtx = NULL;
    m_Codec = NULL;
    m_Frame = NULL;
    m_FrameRGB = NULL;
    m_buffer = NULL;
}


k9AviDecode::~k9AviDecode() {
    if (m_opened)
        close();
}


#include "k9avidecode.moc"


bool k9AviDecode::open(const TQString & _fileName) {
    m_error="";
    if (m_opened)
        close();

    // Open video file
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 2, 0)
    if (avformat_open_input(&m_FormatCtx, _fileName.utf8(), NULL, NULL)!=0)
#else
    if (av_open_input_file(&m_FormatCtx, _fileName.utf8(), NULL, 0, NULL)!=0)
#endif
    {
        m_error=i18n("Couldn't open the file %1").arg(_fileName);
        return false; // Couldn't open file}
    }
// Retrieve stream information
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 6, 0)
    if (avformat_find_stream_info(m_FormatCtx, NULL)<0)
#else
    if (av_find_stream_info(m_FormatCtx)<0)
#endif
    {
        m_error =i18n("Couldn't find stream information");
        return false; // Couldn't find stream information
    }
    unsigned int i;

// Find the first video stream
    m_videoStream=-1;
    for (i=0; i<m_FormatCtx->nb_streams; i++)
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
        if (m_FormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO)
#else
        if (m_FormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
#endif
        {
            m_videoStream=i;
            break;
        }
    if (m_videoStream==-1) {
        m_error=i18n("The file doesn't contain any video stream");
        return false; // Didn't find a video stream
    }

// Find the decoder for the video stream
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
    m_Codec=(AVCodec *)avcodec_find_decoder(m_FormatCtx->streams[m_videoStream]->codecpar->codec_id);
#else
    m_Codec=(AVCodec *)avcodec_find_decoder(m_FormatCtx->streams[m_videoStream]->codec->codec_id);
#endif
    if (m_Codec==NULL) {
        m_error=i18n("Unsupported codec");
        return false; // Codec not found
    }

// Get/allocate a pointer to the codec context for the video stream
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
    m_CodecCtx = avcodec_alloc_context3(m_Codec);
    if (m_CodecCtx) {
        avcodec_parameters_to_context(m_CodecCtx, m_FormatCtx->streams[m_videoStream]->codecpar);
    }
    else {
        m_error=i18n("Failed to allocate a codec context");
        return false;
    }
#else
    m_CodecCtx=m_FormatCtx->streams[m_videoStream]->codec;
#endif

// Open codec
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 8, 0)
    if (avcodec_open2(m_CodecCtx, m_Codec, NULL)<0)
#else
    if (avcodec_open(m_CodecCtx, m_Codec)<0)
#endif
    {
        m_error =i18n("Could'nt open the codec");
        return false; // Could not open codec
    }


// Allocate video frame
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
    m_Frame=av_frame_alloc();
#else
    m_Frame=avcodec_alloc_frame();
#endif

// Allocate an AVFrame structure
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 45, 101)
    m_FrameRGB=av_frame_alloc();
#else
    m_FrameRGB=avcodec_alloc_frame();
#endif
    if (m_FrameRGB==NULL) {
        m_error =i18n ("Unable to allocate memory for frames");
        return false;
    }


    int numBytes;
// Determine required buffer size and allocate buffer
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
    numBytes=av_image_get_buffer_size(AV_PIX_FMT_RGB24, m_CodecCtx->width,
                                m_CodecCtx->height, 1);
#else
    numBytes=avpicture_get_size(AV_PIX_FMT_RGB24, m_CodecCtx->width,
                                m_CodecCtx->height);
#endif
    m_buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 24, 100)
    av_image_fill_arrays(m_FrameRGB->data, m_FrameRGB->linesize, m_buffer,
                   AV_PIX_FMT_RGB24, m_CodecCtx->width, m_CodecCtx->height, 1);
#else
    avpicture_fill((AVPicture *)m_FrameRGB, m_buffer, AV_PIX_FMT_RGB24,
                   m_CodecCtx->width, m_CodecCtx->height);
#endif

    m_duration=(double)m_FormatCtx->duration / AV_TIME_BASE;
    m_opened=true;
    m_fileName=_fileName;
    return true;
}

void k9AviDecode::seek(double _seconds) {
    AVRational time_base = m_FormatCtx->streams[m_videoStream]->time_base;
    int64_t fspos = (int64_t)(_seconds * AV_TIME_BASE);
    fspos=av_rescale_q(fspos, AV_TIME_BASE_Q,  time_base);
    int i=av_seek_frame(m_FormatCtx, m_videoStream, fspos, AVSEEK_FLAG_BACKWARD );
    double pos=av_gettime() / 1000000;
}

void k9AviDecode::readFrame(double _seconds) {
    AVRational time_base = m_FormatCtx->streams[m_videoStream]->time_base;
    int64_t fspos = (int64_t)(_seconds * AV_TIME_BASE);
    fspos=av_rescale_q(fspos, AV_TIME_BASE_Q,  time_base);
    int res=av_seek_frame(m_FormatCtx, m_videoStream, fspos, AVSEEK_FLAG_BACKWARD );
    avcodec_flush_buffers(m_CodecCtx);
    int frameFinished=0;
    AVPacket *packet;

#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100)
    packet = av_packet_alloc();
#else
    AVPacket _packet;
    av_init_packet(&_packet);
    packet = &_packet;
#endif

    struct SwsContext *toRGB_convert_ctx;
    bool bFound=false;
    while (av_read_frame(m_FormatCtx, packet)>=0 &&  !bFound) {
        // Is this a packet from the video stream?
        if (packet->stream_index==m_videoStream) {
            // Decode video frame
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 106, 100)
            int ret = avcodec_receive_frame(m_CodecCtx, m_Frame);
            if (ret == 0)
                frameFinished = 1;
            else if (ret == AVERROR(EAGAIN))
                ret = 0;
            if (ret == 0)
                ret = avcodec_send_packet(m_CodecCtx, packet);
#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 23, 0)
            avcodec_decode_video2(m_CodecCtx, m_Frame, &frameFinished, packet);
#else
            avcodec_decode_video(m_CodecCtx, m_Frame, &frameFinished,
                                 packet->data, packet->size);
#endif

            // Did we get a video frame?
            if (frameFinished) {
                int64_t cur_dts=fspos;
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(54, 2, 0)
                cur_dts = packet->dts;
#else
                if (m_FormatCtx->cur_st)
                    cur_dts = m_FormatCtx->cur_st->cur_dts;
#endif
                if (cur_dts >=fspos) {
                    bFound=true;
                    toRGB_convert_ctx=sws_getContext(m_CodecCtx->width, m_CodecCtx->height, m_CodecCtx->pix_fmt, m_CodecCtx->width, m_CodecCtx->height, AV_PIX_FMT_RGB24, sws_flags,NULL,NULL,NULL);
                    sws_scale(toRGB_convert_ctx, m_Frame->data, m_Frame->linesize, 0, m_CodecCtx->height, m_FrameRGB->data,m_FrameRGB->linesize);
                    // convert frame to QImage
                    SaveFrame(m_FrameRGB, m_CodecCtx->width,
                              m_CodecCtx->height);
                    sws_freeContext(toRGB_convert_ctx);
                }
            }
        }

#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 25, 100)
        // Unreference the packet from av_read_frame
        av_packet_unref(packet);
#else
        // Free the packet from av_read_frame
        av_free_packet(packet);
#endif
    }

#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 12, 100)
    av_packet_free(&packet);
#endif
}

void k9AviDecode::SaveFrame(AVFrame *pFrame, int width, int height) {
    TQImage pix;
    int len =(int) (3*width*height);
    char c[255];
    // Write header
    sprintf(c,"P6\n%d %d\n255\n", width, height);
    char *s= (char*) malloc(len+strlen(c));
    tc_memcpy(s,c,strlen(c));
    tc_memcpy(s+strlen(c),pFrame->data[0], len);
    pix.loadFromData((uchar*)s,strlen(c)+len);
    free(s);
    emit drawFrame( &pix);

}

void k9AviDecode::close() {
    if (m_opened) {
        // Free the RGB image
        av_free(m_buffer);
        av_free(m_FrameRGB);

        // Free the YUV frame
        av_free(m_Frame);

        // Close the codec
        avcodec_close(m_CodecCtx);

        // Close the video file
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 17, 0)
        avformat_close_input(&m_FormatCtx);
#else
        av_close_input_file(m_FormatCtx);
#endif

#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)
        avcodec_free_context(&m_CodecCtx);
#endif

        m_opened=false;
    }
}


double k9AviDecode::getDuration() const {
    return m_duration;
}


bool k9AviDecode::opened() const {
    return m_opened;
}


TQString k9AviDecode::getFileName() const {
    return m_fileName;
}


TQString k9AviDecode::getError() const {
    return m_error;
}
