diff --git a/avdecoder.cpp b/avdecoder.cpp new file mode 100644 index 0000000..593ddd0 --- /dev/null +++ b/avdecoder.cpp @@ -0,0 +1,214 @@ +#include "avdecoder.h" + +#include +#include + +AVDecoder::AVDecoder() : + pFormatCtx(NULL) +{ +} + +bool AVDecoder::open(const QString filename) +{ + if(avformat_open_input(&pFormatCtx, filename.toStdString().c_str(), NULL, NULL) != 0) { + return false; + } + + if(avformat_find_stream_info(pFormatCtx, NULL) < 0) { + avformat_close_input(&pFormatCtx); + return false; + } + + return true; +} + +void AVDecoder::getAudioMetadata(metadata_t &metadata) +{ + AVDictionaryEntry *entry; + AVDictionary* file_metadata = pFormatCtx->metadata; + + if((entry = av_dict_get(file_metadata, "artist", NULL, 0)) != NULL) { + metadata.data.music.artist = strdup(entry->value); + } else { + metadata.data.music.artist = strdup(""); + } + + if((entry = av_dict_get(file_metadata, "album", NULL, 0)) != NULL) { + metadata.data.music.album = strdup(entry->value); + } else { + metadata.data.music.album = strdup(""); + } + + if((entry = av_dict_get(file_metadata, "title", NULL, 0)) != NULL) { + metadata.data.music.title = strdup(entry->value); + } else { + metadata.data.music.title = strdup(""); + } + + av_dict_free(&file_metadata); +} + +void AVDecoder::getVideoMetadata(metadata_t &metadata) +{ + AVDictionaryEntry *entry; + AVDictionary* file_metadata = pFormatCtx->metadata; + + if((entry = av_dict_get(file_metadata, "copyright", NULL, 0)) != NULL) { + metadata.data.video.copyright = strdup(entry->value); + } else { + metadata.data.video.copyright = strdup(""); + } + + if((entry = av_dict_get(file_metadata, "comments", NULL, 0)) != NULL) { + metadata.data.video.explanation = strdup(entry->value); + } else { + metadata.data.video.explanation = strdup(""); + } + + if((entry = av_dict_get(file_metadata, "title", NULL, 0)) != NULL) { + metadata.data.video.title = strdup(entry->value); + } else { + metadata.data.video.title = strdup(""); + } + + av_dict_free(&file_metadata); +} + +QByteArray AVDecoder::getAudioThumbnail(int width, int height) +{ + QByteArray data; + for (uint i = 0; i < pFormatCtx->nb_streams; i++) { + if(pFormatCtx->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { + AVPacket pkt = pFormatCtx->streams[i]->attached_pic; + + QBuffer imgbuffer(&data); + imgbuffer.open(QIODevice::WriteOnly); + QImage img = QImage::fromData(QByteArray((const char *)pkt.data, pkt.size)); + QImage result = img.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + result.save(&imgbuffer, "JPEG"); + av_free_packet(&pkt); + break; + } + } + return data; +} + +void AVDecoder::AVFrameToQImage(AVFrame &frame, QImage &image, int width, int height) +{ + quint8 *src = frame.data[0]; + + for (int y = 0; y < height; y++) { + QRgb *scanLine = (QRgb *)image.scanLine(y); + + for (int x = 0; x < width; x++) { + scanLine[x] = qRgb(src[3*x], src[3*x+1], src[3*x+2]); + } + src += frame.linesize[0]; + } +} + +AVFrame *AVDecoder::getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index) +{ + AVFrame *pFrame = avcodec_alloc_frame(); + + AVPacket packet; + int frame_finished = 0; + + while(!frame_finished && av_read_frame(pFormatCtx, &packet)>=0) { + if(packet.stream_index == stream_index) { + avcodec_decode_video2(pCodecCtx, pFrame, &frame_finished, &packet); + } + av_free_packet(&packet); + } + + if(frame_finished) { + return pFrame; + } else { + av_free(pFrame); + return NULL; + } +} + +QByteArray AVDecoder::getVideoThumbnail(int width, int height) +{ + QByteArray data; + int stream_index; + AVFrame *pFrame; + AVCodec *codec = NULL; + AVCodecContext *pCodecCtx = NULL; + + int percentage = QSettings().value("videoThumbnailSeekPercentage", 30).toInt(); + + if((stream_index = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) < 0) { + return data; + } + + pCodecCtx = pFormatCtx->streams[stream_index]->codec; + + if(avcodec_open2(pCodecCtx, codec, NULL) < 0) { + avcodec_close(pCodecCtx); + return data; + } + + if(av_seek_frame(pFormatCtx, stream_index, pFormatCtx->duration * percentage / 100, 0) < 0) { + avcodec_close(pCodecCtx); + return data; + } + + if((pFrame = getDecodedFrame(pCodecCtx, stream_index)) == NULL) { + avcodec_close(pCodecCtx); + return data; + } + + AVFrame *pFrameRGB = avcodec_alloc_frame(); + + int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); + uint8_t *buffer = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t)); + + avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); + + SwsContext *sws_ctx = sws_getContext( + pCodecCtx->width, + pCodecCtx->height, + pCodecCtx->pix_fmt, + pCodecCtx->width, + pCodecCtx->height, + PIX_FMT_RGB24, + SWS_BILINEAR, + NULL, + NULL, + NULL + ); + + QImage img(pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB32); + + sws_scale( + sws_ctx, + (uint8_t const * const *)pFrame->data, + pFrame->linesize, + 0, + pCodecCtx->height, + pFrameRGB->data, + pFrameRGB->linesize + ); + + AVFrameToQImage(*pFrame, img, pCodecCtx->width, pCodecCtx->height); + + QBuffer imgbuffer(&data); + imgbuffer.open(QIODevice::WriteOnly); + QImage result = img.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); + result.save(&imgbuffer, "JPEG"); + + av_free(buffer); + av_free(pFrameRGB); + av_free(pFrame); + + avcodec_close(pCodecCtx); + + return data; +} + +void AVDecoder::close() +{ + avformat_close_input(&pFormatCtx); +} diff --git a/avdecoder.h b/avdecoder.h new file mode 100644 index 0000000..afd82de --- /dev/null +++ b/avdecoder.h @@ -0,0 +1,32 @@ +#ifndef AVDECODER_H +#define AVDECODER_H + +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +class AVDecoder +{ +public: + AVDecoder(); + bool open(const QString filename); + QByteArray getAudioThumbnail(int width, int height); + QByteArray getVideoThumbnail(int width, int height); + void getAudioMetadata(metadata_t &metadata); + void getVideoMetadata(metadata_t &metadata); + void close(); + +private: + void AVFrameToQImage(AVFrame &frame, QImage &image, int width, int height); + AVFrame *getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index); + + AVFormatContext *pFormatCtx; +}; + +#endif // AVDECODER_H diff --git a/cmaclient.cpp b/cmaclient.cpp index 10c6cd0..2ca60cc 100644 --- a/cmaclient.cpp +++ b/cmaclient.cpp @@ -23,7 +23,6 @@ #include "wirelessworker.h" #include "QApplication" -#include #include #include #include @@ -852,8 +851,7 @@ void CmaClient::vitaEventGetPartOfObject(vita_event_t *event, int eventId) } else { file.seek(part_init.offset); file.write((const char *)data, part_init.size); - object->metadata.size += part_init.size; - object->updateParentSize(part_init.size); + object->updateObjectSize(part_init.size); qDebug("Written %zu bytes to %s at offset %zu.", part_init.size, object->path.toStdString().c_str(), part_init.offset); VitaMTP_ReportResult(device, eventId, PTP_RC_OK); } diff --git a/cmaobject.cpp b/cmaobject.cpp index bb83c6d..6d35124 100644 --- a/cmaobject.cpp +++ b/cmaobject.cpp @@ -175,7 +175,7 @@ void CMAObject::initObject(const QFileInfo &file) metadata.type = VITA_DIR_TYPE_MASK_REGULAR; // ignored for files metadata.dateTimeCreated = file.created().toTime_t(); - metadata.size = file.size(); + metadata.size = 0; DataType type = file.isFile() ? File : Folder; metadata.dataType = (DataType)(type | (parent->metadata.dataType & ~Folder)); @@ -225,7 +225,7 @@ void CMAObject::initObject(const QFileInfo &file) metadata.path = strdup(newpath.toUtf8().data()); } - updateParentSize(metadata.size); + updateObjectSize(file.size()); } bool CMAObject::removeReferencedObject() @@ -237,12 +237,12 @@ bool CMAObject::removeReferencedObject() } } -void CMAObject::updateParentSize(unsigned long size) +void CMAObject::updateObjectSize(unsigned long size) { if(parent) { - parent->metadata.size += size; - parent->updateParentSize(size); + parent->updateObjectSize(size); } + metadata.size += size; } void CMAObject::rename(const QString &newname) diff --git a/cmaobject.h b/cmaobject.h index 7606ac7..ccff70e 100644 --- a/cmaobject.h +++ b/cmaobject.h @@ -39,7 +39,7 @@ public: void rename(const QString &name); bool removeReferencedObject(); void initObject(const QFileInfo &file); - void updateParentSize(unsigned long size); + void updateObjectSize(unsigned long size); bool hasParent(const CMAObject *obj); bool operator==(const CMAObject &obj); diff --git a/qcma.pro b/qcma.pro index 610aaa7..dbbe44a 100644 --- a/qcma.pro +++ b/qcma.pro @@ -26,7 +26,8 @@ SOURCES += main.cpp \ baseworker.cpp \ sforeader.cpp \ cmaclient.cpp \ - cmabroadcast.cpp + cmabroadcast.cpp \ + avdecoder.cpp HEADERS += \ wirelessworker.h \ @@ -41,10 +42,11 @@ HEADERS += \ baseworker.h \ sforeader.h \ cmaclient.h \ - cmabroadcast.h + cmabroadcast.h \ + avdecoder.h CONFIG += link_pkgconfig -PKGCONFIG += libvitamtp libmediainfo +PKGCONFIG += libvitamtp libmediainfo libavformat libavcodec libavutil libswscale QMAKE_CXXFLAGS += -Wno-write-strings -Wall