/* * Copyright (c) 2023 Fancy Code. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "FfmpegThumbnail.h" #include "FfmpegDecoder.h" #include "FfmpegEncoder.h" #include "ILog.h" #include "LinuxApi.h" #include #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __cplusplus } #endif #include #include #include #include #include thumbnail_info::thumbnail_info(const int &srouceWidth, const int &srouceHeight, const int &targetWidth, const int &targetHeight) : mSrouceWidth(srouceWidth), mSrouceHeight(srouceHeight), mTargetWidth(targetWidth), mTargetHeight(targetHeight) { } FfmpegThumbnail::FfmpegThumbnail(const AVCodecID &encodecId, const AVCodecID &decodecId) : mOutputFormat(nullptr), mStream(nullptr), mSwsCtx(nullptr), mEncodecId(encodecId), mDecodecId(decodecId) { } void FfmpegThumbnail::Init(const ThumbnailInfo &thumbnailInfo) { LogInfo("FfmpegThumbnail Init\n"); mSrouceWidth = thumbnailInfo.mSrouceWidth; mSrouceHeight = thumbnailInfo.mSrouceHeight; mTargetWidth = thumbnailInfo.mTargetWidth; mTargetHeight = thumbnailInfo.mTargetHeight; mDecoder = std::make_shared(mDecodecId, mSrouceWidth, mSrouceHeight); if (!mDecoder) { LogError("mDecoder = nullptr.\n"); } mEncoder = std::make_shared(mEncodecId, mTargetWidth, mTargetHeight); if (!mEncoder) { LogError("mEncoder = nullptr.\n"); } } void FfmpegThumbnail::UnInit(void) { if (mOutputFormat && mOutputFormat->pb) { av_write_trailer(mOutputFormat); } if (mEncoder) { mEncoder->UnInit(); mEncoder.reset(); } if (mDecoder) { mDecoder->UnInit(); mDecoder.reset(); } if (nullptr == mOutputFormat) { return; } if (!(mOutputFormat->oformat->flags & AVFMT_NOFILE)) { /* Close the output file. */ avio_closep(&mOutputFormat->pb); } avformat_free_context(mOutputFormat); fx_system_v2("sync"); if (mSwsCtx) { sws_freeContext(mSwsCtx); mSwsCtx = nullptr; } } bool FfmpegThumbnail::CreateThumbnail(const std::string &outputFile, const void *data, const size_t &size) { if (!mDecoder) { LogError("CreateThumbnail mDecoder && mDecodeCallback\n"); return true; } mDecodeCallback = std::bind(&FfmpegThumbnail::GetDecodeDataCallback, this, std::placeholders::_1); mEncodeCallback = std::bind(&FfmpegThumbnail::GetEncodeDataCallback, this, std::placeholders::_1, outputFile); /* allocate the output media context */ avformat_alloc_output_context2(&mOutputFormat, nullptr, "image2", outputFile.c_str()); if (!mOutputFormat) { LogError("Could not deduce output format from file.\n"); return false; } /* Add the audio and video streams using the default format codecs * and initialize the codecs. */ if (mOutputFormat->oformat->video_codec != AV_CODEC_ID_NONE) { /** * @brief Maybe there is no need to use avformat_alloc_output_context2 function to create ffmpeg container. * TODO: if mOutputFormat can be deleted here? */ mStream = avformat_new_stream(mOutputFormat, nullptr); if (!mStream) { LogError("Could not allocate stream\n"); return false; } mStream->id = mOutputFormat->nb_streams - 1; LogInfo("Create video stream\n"); } mDecoder->Init(); constexpr int NO_FLAGS = 0; mEncoder->Init(NO_FLAGS); mStream->time_base = mEncoder->GetTimeBase(); mEncoder->OpenEncoder(nullptr, mStream); LogInfo("Start to decode data\n"); mDecoder->DecodeData(data, size, AV_NOPTS_VALUE, mDecodeCallback); LogInfo("Decode data end\n"); return false; } void FfmpegThumbnail::GetDecodeDataCallback(AVFrame *frame) { EncodeDataToPicture(frame); } void FfmpegThumbnail::GetEncodeDataCallback(AVPacket *pkt, const std::string &fileName) { SaveThumbnailFile(fileName, pkt->data, pkt->size); } void FfmpegThumbnail::EncodeDataToPicture(AVFrame *frame) { LogInfo("Decode frame->width = %d, frame->height=%d\n", frame->width, frame->height); AVFrame *thumbnailFrame = av_frame_alloc(); if (!thumbnailFrame) { LogError("thumbnailFrame = nullptr.\n"); return; } thumbnailFrame->format = AV_PIX_FMT_YUV420P; thumbnailFrame->width = mTargetWidth; thumbnailFrame->height = mTargetHeight; int jpegBufSize = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, mSrouceWidth, mSrouceHeight, 1); LogInfo("jpegBufSize: %d\n", jpegBufSize); uint8_t *jpegBuf = (uint8_t *)av_malloc(jpegBufSize); if (!jpegBuf) { LogError("jpegBuf = nullptr.\n"); goto END; } av_image_fill_arrays( thumbnailFrame->data, thumbnailFrame->linesize, jpegBuf, AV_PIX_FMT_YUV420P, frame->width, frame->height, 1); mSwsCtx = sws_getContext(mSrouceWidth, mSrouceHeight, static_cast(frame->format), thumbnailFrame->width, thumbnailFrame->height, AV_PIX_FMT_YUV420P, SWS_BILINEAR, nullptr, nullptr, nullptr); if (!mSwsCtx) { LogError("mSwsCtx = nullptr.\n"); goto END; } fx_system_v2("echo 1 > /proc/sys/vm/drop_caches"); /** * @brief Perform pixel format conversion. * NOTE: This function will crash when the chip does not have enough memory. */ sws_scale(mSwsCtx, frame->data, frame->linesize, 0, frame->height, thumbnailFrame->data, thumbnailFrame->linesize); if (mEncoder) { mEncoder->EncodeData(thumbnailFrame, mStream, mEncodeCallback); } END: if (thumbnailFrame) { av_frame_free(&thumbnailFrame); } if (jpegBuf) { av_free(jpegBuf); } return; } bool FfmpegThumbnail::SaveThumbnailFile(const std::string &fileName, const void *data, const size_t &size) { FILE *file = nullptr; if (!data) { LogError("SaveThumbnailFile:%s failed, data is nullptr.\n", fileName.c_str()); return false; } LogInfo("SaveThumbnailFile:%s, size = %u\n", fileName.c_str(), size); file = fopen(fileName.c_str(), "a+"); if (!file) { LogError("fopen failed.\n"); return false; } fwrite(data, 1, size, file); fflush(file); fclose(file); // system("sync"); return true; }