// Copyright (c) 2007-2009 Fredrik Mellbin // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "videosource.h" void FFMS_VideoSource::GetFrameCheck(int n) { if (n < 0 || n >= VP.NumFrames) throw FFMS_Exception(FFMS_ERROR_DECODING, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds frame requested"); } void FFMS_VideoSource::SetPP(const char *PP) { if (PPMode) pp_free_mode(PPMode); PPMode = NULL; if (PP != NULL && strcmp(PP, "")) { PPMode = pp_get_mode_by_name_and_quality(PP, PP_QUALITY_MAX); if (!PPMode) { ResetPP(); throw FFMS_Exception(FFMS_ERROR_POSTPROCESSING, FFMS_ERROR_INVALID_ARGUMENT, "Invalid postprocesing settings"); } } ReAdjustPP(CodecContext->pix_fmt, CodecContext->width, CodecContext->height); OutputFrame(DecodeFrame); } void FFMS_VideoSource::ResetPP() { if (PPContext) pp_free_context(PPContext); PPContext = NULL; if (PPMode) pp_free_mode(PPMode); PPMode = NULL; OutputFrame(DecodeFrame); } void FFMS_VideoSource::ReAdjustPP(PixelFormat VPixelFormat, int Width, int Height) { if (PPContext) pp_free_context(PPContext); PPContext = NULL; if (!PPMode) return; int Flags = GetPPCPUFlags(); switch (VPixelFormat) { case PIX_FMT_YUV420P: Flags |= PP_FORMAT_420; break; case PIX_FMT_YUV422P: Flags |= PP_FORMAT_422; break; case PIX_FMT_YUV411P: Flags |= PP_FORMAT_411; break; case PIX_FMT_YUV444P: Flags |= PP_FORMAT_444; break; default: ResetPP(); throw FFMS_Exception(FFMS_ERROR_POSTPROCESSING, FFMS_ERROR_UNSUPPORTED, "The video does not have a colorspace suitable for postprocessing"); } PPContext = pp_get_context(Width, Height, Flags); avpicture_free(&PPFrame); avpicture_alloc(&PPFrame, VPixelFormat, Width, Height); } static void CopyAVPictureFields(AVPicture &Picture, FFMS_Frame &Dst) { for (int i = 0; i < 4; i++) { Dst.Data[i] = Picture.data[i]; Dst.Linesize[i] = Picture.linesize[i]; } } FFMS_Frame *FFMS_VideoSource::OutputFrame(AVFrame *Frame) { if (LastFrameWidth != CodecContext->width || LastFrameHeight != CodecContext->height || LastFramePixelFormat != CodecContext->pix_fmt) { ReAdjustPP(CodecContext->pix_fmt, CodecContext->width, CodecContext->height); if (TargetHeight > 0 && TargetWidth > 0 && TargetPixelFormats != 0) ReAdjustOutputFormat(TargetPixelFormats, TargetWidth, TargetHeight, TargetResizer); } if (PPMode) { pp_postprocess(const_cast(Frame->data), Frame->linesize, PPFrame.data, PPFrame.linesize, CodecContext->width, CodecContext->height, Frame->qscale_table, Frame->qstride, PPMode, PPContext, Frame->pict_type | (Frame->qscale_type ? PP_PICT_TYPE_QP2 : 0)); if (SWS) { sws_scale(SWS, PPFrame.data, PPFrame.linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); CopyAVPictureFields(SWSFrame, LocalFrame); } else { CopyAVPictureFields(PPFrame, LocalFrame); } } else { if (SWS) { sws_scale(SWS, Frame->data, Frame->linesize, 0, CodecContext->height, SWSFrame.data, SWSFrame.linesize); CopyAVPictureFields(SWSFrame, LocalFrame); } else { // Special case to avoid ugly casts for (int i = 0; i < 4; i++) { LocalFrame.Data[i] = Frame->data[i]; LocalFrame.Linesize[i] = Frame->linesize[i]; } } } LocalFrame.EncodedWidth = CodecContext->width; LocalFrame.EncodedHeight = CodecContext->height; LocalFrame.EncodedPixelFormat = CodecContext->pix_fmt; LocalFrame.ScaledWidth = TargetWidth; LocalFrame.ScaledHeight = TargetHeight; LocalFrame.ConvertedPixelFormat = OutputFormat; LocalFrame.KeyFrame = Frame->key_frame; LocalFrame.PictType = av_get_pict_type_char(Frame->pict_type); LocalFrame.RepeatPict = Frame->repeat_pict; LocalFrame.InterlacedFrame = Frame->interlaced_frame; LocalFrame.TopFieldFirst = Frame->top_field_first; LastFrameHeight = CodecContext->height; LastFrameWidth = CodecContext->width; LastFramePixelFormat = CodecContext->pix_fmt; return &LocalFrame; } FFMS_VideoSource::FFMS_VideoSource(const char *SourceFile, FFMS_Index *Index, int Track) { if (Track < 0 || Track >= static_cast(Index->size())) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Out of bounds track index selected"); if (Index->at(Track).TT != FFMS_TYPE_VIDEO) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Not a video track"); if (Index[Track].size() == 0) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_INVALID_ARGUMENT, "Video track contains no frames"); if (!Index->CompareFileSignature(SourceFile)) throw FFMS_Exception(FFMS_ERROR_INDEX, FFMS_ERROR_FILE_MISMATCH, "The index does not match the source file"); memset(&VP, 0, sizeof(VP)); PPContext = NULL; PPMode = NULL; SWS = NULL; LastFrameNum = 0; CurrentFrame = 1; MPEG4Counter = 0; CodecContext = NULL; LastFrameHeight = -1; LastFrameWidth = -1; LastFramePixelFormat = PIX_FMT_NONE; TargetHeight = -1; TargetWidth = -1; TargetPixelFormats = 0; TargetResizer = 0; OutputFormat = PIX_FMT_NONE; DecodeFrame = avcodec_alloc_frame(); // Dummy allocations so the unallocated case doesn't have to be handled later avpicture_alloc(&PPFrame, PIX_FMT_GRAY8, 16, 16); avpicture_alloc(&SWSFrame, PIX_FMT_GRAY8, 16, 16); } FFMS_VideoSource::~FFMS_VideoSource() { if (PPMode) pp_free_mode(PPMode); if (PPContext) pp_free_context(PPContext); if (SWS) sws_freeContext(SWS); avpicture_free(&PPFrame); avpicture_free(&SWSFrame); av_freep(&DecodeFrame); } FFMS_Frame *FFMS_VideoSource::GetFrameByTime(double Time) { int Frame = Frames.ClosestFrameFromPTS(static_cast((Time * 1000 * Frames.TB.Den) / Frames.TB.Num)); return GetFrame(Frame); } void FFMS_VideoSource::SetOutputFormat(int64_t TargetFormats, int Width, int Height, int Resizer) { this->TargetWidth = Width; this->TargetHeight = Height; this->TargetPixelFormats = TargetFormats; this->TargetResizer = Resizer; ReAdjustOutputFormat(TargetFormats, Width, Height, Resizer); OutputFrame(DecodeFrame); } void FFMS_VideoSource::ReAdjustOutputFormat(int64_t TargetFormats, int Width, int Height, int Resizer) { if (SWS) { sws_freeContext(SWS); SWS = NULL; } int Loss; OutputFormat = avcodec_find_best_pix_fmt(TargetFormats, CodecContext->pix_fmt, 1 /* Required to prevent pointless RGB32 => RGB24 conversion */, &Loss); if (OutputFormat == PIX_FMT_NONE) { ResetOutputFormat(); throw FFMS_Exception(FFMS_ERROR_SCALING, FFMS_ERROR_INVALID_ARGUMENT, "No suitable output format found"); } if (CodecContext->pix_fmt != OutputFormat || Width != CodecContext->width || Height != CodecContext->height) { SWS = sws_getContext(CodecContext->width, CodecContext->height, CodecContext->pix_fmt, Width, Height, OutputFormat, GetSWSCPUFlags() | Resizer, NULL, NULL, NULL); if (SWS == NULL) { ResetOutputFormat(); throw FFMS_Exception(FFMS_ERROR_SCALING, FFMS_ERROR_INVALID_ARGUMENT, "Failed to allocate SWScale context"); } } avpicture_free(&SWSFrame); avpicture_alloc(&SWSFrame, OutputFormat, Width, Height); } void FFMS_VideoSource::ResetOutputFormat() { if (SWS) { sws_freeContext(SWS); SWS = NULL; } TargetWidth = -1; TargetHeight = -1; TargetPixelFormats = 0; OutputFormat = PIX_FMT_NONE; OutputFrame(DecodeFrame); }