/*
* QCMA: Cross-platform content manager assistant for the PS Vita
*
* Copyright (C) 2013 Codestation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "cmautils.h"
#include "avdecoder.h"
#include "database.h"
#include
#include
#include
#include
AVDecoder::AvInit init;
AVDecoder::AVDecoder() :
pFormatCtx(NULL), pCodecCtx(NULL), av_stream(NULL), av_codec(NULL), stream_index(-1), codec_loaded(false)
{
}
AVDecoder::~AVDecoder()
{
if(pCodecCtx) {
avcodec_close(pCodecCtx);
}
if(pFormatCtx) {
avformat_close_input(&pFormatCtx);
}
}
bool AVDecoder::open(const QString filename)
{
if(avformat_open_input(&pFormatCtx, QFile::encodeName(filename).constData(), NULL, NULL) != 0) {
return false;
}
if(avformat_find_stream_info(pFormatCtx, NULL) < 0) {
avformat_close_input(&pFormatCtx);
return false;
}
return true;
}
bool AVDecoder::loadCodec(codec_type codec)
{
if(codec_loaded) {
return true;
}
AVDictionary *opts = NULL;
if((stream_index = av_find_best_stream(pFormatCtx, (AVMediaType)codec, -1, -1, &av_codec, 0)) < 0) {
return false;
}
av_stream = pFormatCtx->streams[stream_index];
pCodecCtx = av_stream->codec;
if(avcodec_open2(pCodecCtx, av_codec, &opts) < 0) {
codec_loaded = false;
return false;
}
codec_loaded = true;
return true;
}
const char *AVDecoder::getMetadataEntry(const char *key, const char *default_value)
{
AVDictionaryEntry *entry;
if((entry = av_dict_get(pFormatCtx->metadata, key, NULL, 0)) != NULL) {
return entry->value;
}
return default_value;
}
void AVDecoder::getAudioMetadata(metadata_t &metadata)
{
metadata.data.music.artist = strdup(getMetadataEntry("artist", ""));
metadata.data.music.album = strdup(getMetadataEntry("album", ""));
metadata.data.music.title = strdup(getMetadataEntry("title", ""));
if(loadCodec(CODEC_AUDIO)) {
metadata.data.music.tracks->data.track_audio.bitrate = pCodecCtx->bit_rate;
} else {
metadata.data.music.tracks->data.track_audio.bitrate = pFormatCtx->bit_rate;
}
}
void AVDecoder::getVideoMetadata(metadata_t &metadata)
{
metadata.data.video.copyright = strdup(getMetadataEntry("copyright", ""));
metadata.data.video.explanation = strdup(getMetadataEntry("comments", ""));
metadata.data.video.title = strdup(getMetadataEntry("title", metadata.name));
if(loadCodec(CODEC_VIDEO)) {
metadata.data.video.tracks->data.track_video.width = pCodecCtx->width;
metadata.data.video.tracks->data.track_video.height = pCodecCtx->height;
metadata.data.video.tracks->data.track_video.bitrate = pCodecCtx->bit_rate;
metadata.data.video.tracks->data.track_video.duration = pFormatCtx->duration / 1000;
if(strcmp(av_codec->name, "h264") == 0) {
metadata.data.video.tracks->data.track_video.codecType = CODEC_TYPE_AVC;
} else if(strcmp(av_codec->name, "mpeg4") == 0) {
metadata.data.video.tracks->data.track_video.codecType = CODEC_TYPE_MPEG4;
} else {
metadata.data.video.tracks->data.track_video.codecType = 0;
}
}
}
AVFrame *AVDecoder::getDecodedFrame(AVCodecContext *codec_ctx, int frame_stream_index)
{
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
AVFrame *pFrame = av_frame_alloc();
#else
AVFrame *pFrame = avcodec_alloc_frame();
#endif
AVPacket packet;
int frame_finished = 0;
while(!frame_finished && av_read_frame(pFormatCtx, &packet)>=0) {
if(packet.stream_index == frame_stream_index) {
avcodec_decode_video2(codec_ctx, pFrame, &frame_finished, &packet);
}
av_free_packet(&packet);
}
if(frame_finished) {
return pFrame;
} else {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrame);
#else
av_free(pFrame);
#endif
return NULL;
}
}
bool AVDecoder::seekVideo(int percentage)
{
if(!loadCodec(CODEC_VIDEO)) {
return false;
}
qint64 seek_pos = pFormatCtx->duration * percentage / (AV_TIME_BASE * 100);
qint64 frame = av_rescale(seek_pos, av_stream->time_base.den, av_stream->time_base.num);
return avformat_seek_file(pFormatCtx, stream_index, 0, frame, frame, AVSEEK_FLAG_FRAME) >= 0;
}
static void calculate_thumbnail_dimensions(int src_width, int src_height,
int src_sar_num, int src_sar_den,
int &dst_width, int &dst_height) {
if ((src_sar_num <= 0) || (src_sar_den <= 0)) {
src_sar_num = 1;
src_sar_den = 1;
}
if ((src_width * src_sar_num) / src_sar_den > src_height) {
dst_width = 256;
dst_height = (dst_width * src_height) / ((src_width * src_sar_num) / src_sar_den);
} else {
dst_height = 256;
dst_width = (dst_height * ((src_width * src_sar_num) / src_sar_den)) / src_height;
}
if (dst_width < 8)
dst_width = 8;
if (dst_height < 1)
dst_height = 1;
}
QByteArray AVDecoder::getThumbnail(int &width, int &height)
{
QByteArray data;
if(!loadCodec(CODEC_VIDEO)) {
return data;
}
AVFrame *pFrame = getDecodedFrame(pCodecCtx, stream_index);
if(pFrame != NULL) {
calculate_thumbnail_dimensions(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->sample_aspect_ratio.num,
pCodecCtx->sample_aspect_ratio.den,
width, height);
data = WriteJPEG(pCodecCtx, pFrame, width, height);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrame);
#else
av_free(pFrame);
#endif
}
return data;
}
QByteArray AVDecoder::WriteJPEG(AVCodecContext *pCodecCtx, AVFrame *pFrame, int width, int height)
{
AVCodecContext *pOCodecCtx;
AVCodec *pOCodec;
QByteArray data;
pOCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
if (!pOCodec) {
return data;
}
SwsContext *sws_ctx = sws_getContext(
pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt,
width, height,
PIX_FMT_YUVJ420P, SWS_BICUBIC,
NULL, NULL, NULL);
if(!sws_ctx) {
return data;
}
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
AVFrame *pFrameRGB = av_frame_alloc();
#else
AVFrame *pFrameRGB = avcodec_alloc_frame();
#endif
if(pFrameRGB == NULL) {
sws_freeContext(sws_ctx);
return data;
}
int numBytes = avpicture_get_size(PIX_FMT_YUVJ420P, width, height);
uint8_t *buffer = (uint8_t *)av_malloc(numBytes);
if(!buffer) {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB);
#else
av_free(pFrameRGB);
#endif
sws_freeContext(sws_ctx);
return data;
}
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_YUVJ420P, width, height);
sws_scale(
sws_ctx,
pFrame->data,
pFrame->linesize,
0,
pCodecCtx->height,
pFrameRGB->data,
pFrameRGB->linesize
);
pOCodecCtx = avcodec_alloc_context3(pOCodec);
if(pOCodecCtx == NULL) {
avcodec_free_context(&pOCodecCtx);
av_free(buffer);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB);
#else
av_free(&pFrameRGB);
#endif
sws_freeContext(sws_ctx);
return 0;
}
pOCodecCtx->bit_rate = pCodecCtx->bit_rate;
pOCodecCtx->width = width;
pOCodecCtx->height = height;
pOCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
pOCodecCtx->codec_id = AV_CODEC_ID_MJPEG;
pOCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pOCodecCtx->time_base.num = pCodecCtx->time_base.num;
pOCodecCtx->time_base.den = pCodecCtx->time_base.den;
AVDictionary *opts = NULL;
if(avcodec_open2(pOCodecCtx, pOCodec, &opts) < 0) {
avcodec_free_context(&pOCodecCtx);
av_free(buffer);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB);
#else
av_free(&pFrameRGB);
#endif
sws_freeContext(sws_ctx);
return 0;
}
pOCodecCtx->mb_lmin = pOCodecCtx->lmin = pOCodecCtx->qmin * FF_QP2LAMBDA;
pOCodecCtx->mb_lmax = pOCodecCtx->lmax = pOCodecCtx->qmax * FF_QP2LAMBDA;
pOCodecCtx->flags = CODEC_FLAG_QSCALE;
pOCodecCtx->global_quality = pOCodecCtx->qmin * FF_QP2LAMBDA;
pFrame->pts = 1;
pFrame->quality = pOCodecCtx->global_quality;
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
int gotPacket;
avcodec_encode_video2(pOCodecCtx, &pkt, pFrameRGB, &gotPacket);
QByteArray buffer2(reinterpret_cast(pkt.data), pkt.size);
avcodec_free_context(&pOCodecCtx);
av_free(buffer);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB);
#else
av_free(&pFrameRGB);
#endif
avcodec_close(pOCodecCtx);
sws_freeContext(sws_ctx);
return buffer2;
}
void AVDecoder::close()
{
avformat_close_input(&pFormatCtx);
pFormatCtx = NULL;
}