From 54897d5c06df0e0384e0b59b23a1d926762cc078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Sat, 1 Aug 2009 01:16:12 +0000 Subject: [PATCH] * Also pass the media_codec_info to the Writer::AllocateCookie(), since that info is not part of the media_format otherwise. * Finished enough in the AVFormatWriter and AVCodecEncoder that we can now actually create AVIs and MPGs and encode MPEG1, MPEG2 and MPEG4 video. But no audio as of yet. Also, there is no bit-rate/quality setup, so it seems libavformat is using the least possible bit-rate/quality. * Enable some more muxers and encoders in the FFmpeg libs. * Uses pixel format conversion from libswsscale, need to read the documentation again, but I think it makes the plugin GPL. * Fixed includes in libswscale/swscale.h, this is now an unmodified FFmpeg 0.5 header again (AFAICT). git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@32043 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- headers/private/media/WriterPlugin.h | 3 +- .../media/plugins/ffmpeg/AVCodecEncoder.cpp | 85 ++++++++++++++--- .../media/plugins/ffmpeg/AVCodecEncoder.h | 7 +- .../media/plugins/ffmpeg/AVFormatReader.cpp | 2 +- .../media/plugins/ffmpeg/AVFormatWriter.cpp | 92 ++++++++++++++++--- .../media/plugins/ffmpeg/AVFormatWriter.h | 3 +- .../media/plugins/ffmpeg/EncoderTable.cpp | 40 ++++++-- .../media/plugins/ffmpeg/MuxerTable.cpp | 14 +++ src/add-ons/media/plugins/ffmpeg/config.h | 8 +- .../media/plugins/ffmpeg/libavformat/utils.c | 9 ++ .../media/plugins/ffmpeg/libswscale/swscale.h | 1 - src/kits/media/MediaFile.cpp | 3 +- src/kits/media/MediaWriter.cpp | 2 +- 13 files changed, 224 insertions(+), 45 deletions(-) diff --git a/headers/private/media/WriterPlugin.h b/headers/private/media/WriterPlugin.h index dafca5f7cc..b89eb616bc 100644 --- a/headers/private/media/WriterPlugin.h +++ b/headers/private/media/WriterPlugin.h @@ -21,7 +21,8 @@ public: virtual status_t Close() = 0; virtual status_t AllocateCookie(void** cookie, - const media_format* format) = 0; + const media_format* format, + const media_codec_info* codecInfo) = 0; virtual status_t FreeCookie(void* cookie) = 0; virtual status_t SetCopyright(void* cookie, diff --git a/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.cpp b/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.cpp index 655e98c2ed..2d82c7ea67 100644 --- a/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.cpp +++ b/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.cpp @@ -24,7 +24,7 @@ extern "C" { #endif -static const size_t kDefaultChunkBufferSize = FF_MIN_BUFFER_SIZE; +static const size_t kDefaultChunkBufferSize = 2 * 1024 * 1024; AVCodecEncoder::AVCodecEncoder(uint32 codecID) @@ -32,8 +32,8 @@ AVCodecEncoder::AVCodecEncoder(uint32 codecID) Encoder(), fCodec(NULL), fContext(avcodec_alloc_context()), - fInputPicture(avcodec_alloc_frame()), -// fOutputPicture(avcodec_alloc_frame()), + fFrame(avcodec_alloc_frame()), + fSwsContext(NULL), fCodecInitDone(false), fChunkBuffer(new(std::nothrow) uint8[kDefaultChunkBufferSize]) { @@ -53,8 +53,25 @@ AVCodecEncoder::~AVCodecEncoder() if (fCodecInitDone) avcodec_close(fContext); -// free(fOutputPicture); - free(fInputPicture); + sws_freeContext(fSwsContext); + + avpicture_free(&fDstFrame); + // NOTE: Do not use avpicture_free() on fSrcFrame!! We fill the picture + // data on the file with the media buffer data passed to Encode(). + + if (fFrame != NULL) { + fFrame->data[0] = NULL; + fFrame->data[1] = NULL; + fFrame->data[2] = NULL; + fFrame->data[3] = NULL; + + fFrame->linesize[0] = 0; + fFrame->linesize[1] = 0; + fFrame->linesize[2] = 0; + fFrame->linesize[3] = 0; + free(fFrame); + } + free(fContext); delete[] fChunkBuffer; @@ -94,10 +111,15 @@ AVCodecEncoder::SetUp(const media_format* inputFormat) fInputFormat = *inputFormat; if (fInputFormat.type == B_MEDIA_RAW_VIDEO) { + // frame rate + fContext->time_base.den = (int)fInputFormat.u.raw_video.field_rate; + fContext->time_base.num = 1; + // video size fContext->width = fInputFormat.u.raw_video.display.line_width; fContext->height = fInputFormat.u.raw_video.display.line_count; // fContext->gop_size = 12; - fContext->pix_fmt = PIX_FMT_BGR32; + // TODO: Fix pixel format or setup conversion method... + fContext->pix_fmt = PIX_FMT_YUV420P; // fContext->rate_emu = 0; // TODO: Setup rate control: // fContext->rc_eq = NULL; @@ -111,11 +133,38 @@ AVCodecEncoder::SetUp(const media_format* inputFormat) || fContext->sample_aspect_ratio.den == 0) { av_reduce(&fContext->sample_aspect_ratio.num, &fContext->sample_aspect_ratio.den, fContext->width, - fContext->height, 256); + fContext->height, 255); } // TODO: This should already happen in AcceptFormat() - fInputFormat.u.raw_video.display.bytes_per_row = fContext->width * 4; + if (fInputFormat.u.raw_video.display.bytes_per_row == 0) { + fInputFormat.u.raw_video.display.bytes_per_row + = fContext->width * 4; + } + + fFrame->pts = 0; + + // Allocate space for colorspace converted AVPicture + // TODO: Check allocations... + avpicture_alloc(&fDstFrame, fContext->pix_fmt, fContext->width, + fContext->height); + + // Make the frame point to the data in the converted AVPicture + fFrame->data[0] = fDstFrame.data[0]; + fFrame->data[1] = fDstFrame.data[1]; + fFrame->data[2] = fDstFrame.data[2]; + fFrame->data[3] = fDstFrame.data[3]; + + fFrame->linesize[0] = fDstFrame.linesize[0]; + fFrame->linesize[1] = fDstFrame.linesize[1]; + fFrame->linesize[2] = fDstFrame.linesize[2]; + fFrame->linesize[3] = fDstFrame.linesize[3]; + + // TODO: Use actual pixel format from media_format! + fSwsContext = sws_getContext(fContext->width, fContext->height, + PIX_FMT_RGB32, fContext->width, fContext->height, + fContext->pix_fmt, SWS_BICUBIC, NULL, NULL, NULL); + } else { return B_NOT_SUPPORTED; } @@ -154,6 +203,9 @@ AVCodecEncoder::Encode(const void* buffer, int64 frameCount, { TRACE("AVCodecEncoder::Encode(%p, %lld, %p)\n", buffer, frameCount, info); + if (!fCodecInitDone) + return B_NO_INIT; + if (fInputFormat.type == B_MEDIA_RAW_AUDIO) return _EncodeAudio(buffer, frameCount, info); else if (fInputFormat.type == B_MEDIA_RAW_VIDEO) @@ -192,12 +244,23 @@ AVCodecEncoder::_EncodeVideo(const void* buffer, int64 frameCount, while (frameCount > 0) { size_t bpr = fInputFormat.u.raw_video.display.bytes_per_row; size_t bufferSize = fInputFormat.u.raw_video.display.line_count * bpr; + TRACE(" bytes per row: %ld, buffer size: %ld\n", bpr, bufferSize); - fInputPicture->data[0] = (uint8_t*)buffer; - fInputPicture->linesize[0] = bpr; + // We should always get chunky bitmaps, so this code should be safe. + fSrcFrame.data[0] = (uint8_t*)buffer; + fSrcFrame.linesize[0] = bpr; + // Run the pixel format conversion + sws_scale(fSwsContext, fSrcFrame.data, fSrcFrame.linesize, 0, + fInputFormat.u.raw_video.display.line_count, fDstFrame.data, + fDstFrame.linesize); + + // TODO: Look into this... avcodec.h says we need to set it. + fFrame->pts++; + + // Encode one video chunk/frame. int usedBytes = avcodec_encode_video(fContext, fChunkBuffer, - kDefaultChunkBufferSize, fInputPicture); + kDefaultChunkBufferSize, fFrame); if (usedBytes < 0) { TRACE(" avcodec_encode_video() failed: %d\n", usedBytes); diff --git a/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.h b/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.h index b86a416fae..af53872575 100644 --- a/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.h +++ b/src/add-ons/media/plugins/ffmpeg/AVCodecEncoder.h @@ -10,6 +10,7 @@ extern "C" { #include "avcodec.h" + #include "swscale.h" } #include "EncoderPlugin.h" @@ -51,8 +52,10 @@ private: // TODO: Refactor common base class from AVCodec[De|En]Coder! AVCodec* fCodec; AVCodecContext* fContext; - AVFrame* fInputPicture; -// AVFrame* fOutputPicture; + AVPicture fSrcFrame; + AVPicture fDstFrame; + AVFrame* fFrame; + SwsContext* fSwsContext; uint32 fAVCodecID; diff --git a/src/add-ons/media/plugins/ffmpeg/AVFormatReader.cpp b/src/add-ons/media/plugins/ffmpeg/AVFormatReader.cpp index b4fcba6b6d..aed98da727 100644 --- a/src/add-ons/media/plugins/ffmpeg/AVFormatReader.cpp +++ b/src/add-ons/media/plugins/ffmpeg/AVFormatReader.cpp @@ -26,7 +26,7 @@ extern "C" { #include "gfx_util.h" -#define TRACE_AVFORMAT_READER +//#define TRACE_AVFORMAT_READER #ifdef TRACE_AVFORMAT_READER # define TRACE printf # define TRACE_IO(a...) diff --git a/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.cpp b/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.cpp index 3c9d7338c5..6fa0c9066f 100644 --- a/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.cpp +++ b/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.cpp @@ -58,7 +58,8 @@ public: BLocker* streamLock); virtual ~StreamCookie(); - status_t Init(const media_format* format); + status_t Init(const media_format* format, + const media_codec_info* codecInfo); status_t WriteChunk(const void* chunkBuffer, size_t chunkSize, @@ -70,7 +71,8 @@ public: private: AVFormatContext* fContext; AVStream* fStream; - // Since different threads may read from the source, + AVPacket fPacket; + // Since different threads may write to the target, // we need to protect the file position and I/O by a lock. BLocker* fStreamLock; }; @@ -84,29 +86,64 @@ AVFormatWriter::StreamCookie::StreamCookie(AVFormatContext* context, fStream(NULL), fStreamLock(streamLock) { + av_new_packet(&fPacket, 0); } AVFormatWriter::StreamCookie::~StreamCookie() { + av_free_packet(&fPacket); } status_t -AVFormatWriter::StreamCookie::Init(const media_format* format) +AVFormatWriter::StreamCookie::Init(const media_format* format, + const media_codec_info* codecInfo) { TRACE("AVFormatWriter::StreamCookie::Init()\n"); BAutolock _(fStreamLock); - fStream = av_new_stream(fContext, fContext->nb_streams); + fPacket.stream_index = fContext->nb_streams; + fStream = av_new_stream(fContext, fPacket.stream_index); if (fStream == NULL) { TRACE(" failed to add new stream\n"); return B_ERROR; } - // TODO: Setup the stream according to the media format... + // Setup the stream according to the media format... + if (format->type == B_MEDIA_RAW_VIDEO) { + avcodec_get_context_defaults2(fStream->codec, CODEC_TYPE_VIDEO); + // frame rate + fStream->codec->time_base.den = (int)format->u.raw_video.field_rate; + fStream->codec->time_base.num = 1; + // video size + fStream->codec->width = format->u.raw_video.display.line_width; + fStream->codec->height = format->u.raw_video.display.line_count; + // pixel aspect ratio + fStream->sample_aspect_ratio.num + = format->u.raw_video.pixel_width_aspect; + fStream->sample_aspect_ratio.den + = format->u.raw_video.pixel_height_aspect; + if (fStream->sample_aspect_ratio.num == 0 + || fStream->sample_aspect_ratio.den == 0) { + av_reduce(&fStream->sample_aspect_ratio.num, + &fStream->sample_aspect_ratio.den, fStream->codec->width, + fStream->codec->height, 255); + } + + fStream->codec->sample_aspect_ratio = fStream->sample_aspect_ratio; + // TODO: Don't hard code this... + fStream->codec->pix_fmt = PIX_FMT_YUV420P; + } else if (format->type == B_MEDIA_RAW_AUDIO) { + avcodec_get_context_defaults2(fStream->codec, CODEC_TYPE_AUDIO); + // TODO: ... + } + + // TODO: This is a hack for now! Use avcodec_find_encoder_by_name() + // or something similar... + fStream->codec->codec_id = (CodecID)codecInfo->sub_id; return B_OK; } @@ -116,12 +153,33 @@ status_t AVFormatWriter::StreamCookie::WriteChunk(const void* chunkBuffer, size_t chunkSize, media_encode_info* encodeInfo) { - TRACE("AVFormatWriter::StreamCookie::WriteChunk(%p, %ld)\n", + TRACE_PACKET("AVFormatWriter::StreamCookie::WriteChunk(%p, %ld)\n", chunkBuffer, chunkSize); BAutolock _(fStreamLock); - return B_ERROR; + // TODO: Probably the AVCodecEncoder needs to pass packet data + // in encodeInfo... + + fPacket.data = const_cast((const uint8_t*)chunkBuffer); + fPacket.size = chunkSize; + +#if 0 + // TODO: Eventually, we need to write interleaved packets, but + // maybe we are only supposed to use this if we have actually + // more than one stream. For the moment, this crashes in AVPacket + // shuffling inside libavformat. Maybe if we want to use this, we + // need to allocate a separate AVPacket and copy the chunk buffer. + int result = av_interleaved_write_frame(fContext, &fPacket); + if (result < 0) + TRACE(" av_interleaved_write_frame(): %d\n", result); +#else + int result = av_write_frame(fContext, &fPacket); + if (result < 0) + TRACE(" av_write_frame(): %d\n", result); +#endif + + return result == 0 ? B_OK : B_ERROR; } @@ -183,12 +241,10 @@ AVFormatWriter::Init(const media_file_format* fileFormat) return B_ERROR; } - // TODO: Is this how it works? - // TODO: Is the cookie stored in ByteIOContext? Or does it need to be - // stored in fContext->priv_data? + // Setup I/O hooks. This seems to be enough. fContext->pb = &fIOContext; - // TODO: Set the AVOutputFormat according to fileFormat... + // Set the AVOutputFormat according to fileFormat... fContext->oformat = guess_format(fileFormat->short_name, fileFormat->file_extension, fileFormat->mime_type); if (fContext->oformat == NULL) { @@ -263,7 +319,8 @@ AVFormatWriter::Close() status_t -AVFormatWriter::AllocateCookie(void** _cookie, const media_format* format) +AVFormatWriter::AllocateCookie(void** _cookie, const media_format* format, + const media_codec_info* codecInfo) { TRACE("AVFormatWriter::AllocateCookie()\n"); @@ -275,7 +332,14 @@ AVFormatWriter::AllocateCookie(void** _cookie, const media_format* format) StreamCookie* cookie = new(std::nothrow) StreamCookie(fContext, &fStreamLock); - return cookie->Init(format); + status_t ret = cookie->Init(format, codecInfo); + if (ret != B_OK) { + delete cookie; + return ret; + } + + *_cookie = cookie; + return B_OK; } @@ -327,7 +391,7 @@ AVFormatWriter::WriteChunk(void* _cookie, const void* chunkBuffer, } -// #pragma mark - +// #pragma mark - I/O hooks /*static*/ int diff --git a/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.h b/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.h index 6ff66ce2ff..bc8df27fef 100644 --- a/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.h +++ b/src/add-ons/media/plugins/ffmpeg/AVFormatWriter.h @@ -28,7 +28,8 @@ public: virtual status_t Close(); virtual status_t AllocateCookie(void** cookie, - const media_format* format); + const media_format* format, + const media_codec_info* codecInfo); virtual status_t FreeCookie(void* cookie); virtual status_t SetCopyright(void* cookie, diff --git a/src/add-ons/media/plugins/ffmpeg/EncoderTable.cpp b/src/add-ons/media/plugins/ffmpeg/EncoderTable.cpp index 4f313d1798..572693f98f 100644 --- a/src/add-ons/media/plugins/ffmpeg/EncoderTable.cpp +++ b/src/add-ons/media/plugins/ffmpeg/EncoderTable.cpp @@ -20,22 +20,46 @@ const EncoderDescription gEncoderTable[] = { CODEC_ID_MPEG4, { 0 } }, - B_ANY_FORMAT_FAMILY, + B_ANY_FORMAT_FAMILY, // TODO: Hm, actually not really /any/ family... B_MEDIA_RAW_VIDEO, B_MEDIA_ENCODED_VIDEO }, { { - "MP3 Audio", - "mp3", + "MPEG1 Video", + "mpeg1video", 0, - CODEC_ID_MP3, + CODEC_ID_MPEG1VIDEO, { 0 } }, - B_ANY_FORMAT_FAMILY, - B_MEDIA_RAW_AUDIO, - B_MEDIA_ENCODED_AUDIO - } + B_MPEG_FORMAT_FAMILY, + B_MEDIA_RAW_VIDEO, + B_MEDIA_ENCODED_VIDEO + }, + { + { + "MPEG2 Video", + "mpeg2video", + 0, + CODEC_ID_MPEG2VIDEO, + { 0 } + }, + B_MPEG_FORMAT_FAMILY, + B_MEDIA_RAW_VIDEO, + B_MEDIA_ENCODED_VIDEO + }, +// { +// { +// "MP3 Audio", +// "mp3", +// 0, +// CODEC_ID_MP3, +// { 0 } +// }, +// B_ANY_FORMAT_FAMILY, +// B_MEDIA_RAW_AUDIO, +// B_MEDIA_ENCODED_AUDIO +// } }; const size_t gEncoderCount = sizeof(gEncoderTable) / sizeof(EncoderDescription); diff --git a/src/add-ons/media/plugins/ffmpeg/MuxerTable.cpp b/src/add-ons/media/plugins/ffmpeg/MuxerTable.cpp index 97bae99262..ecb0e9f180 100644 --- a/src/add-ons/media/plugins/ffmpeg/MuxerTable.cpp +++ b/src/add-ons/media/plugins/ffmpeg/MuxerTable.cpp @@ -24,6 +24,20 @@ const media_file_format gMuxerTable[] = { "avi", { 0 } }, + { + media_file_format::B_WRITABLE + | media_file_format::B_KNOWS_ENCODED_VIDEO + | media_file_format::B_KNOWS_ENCODED_AUDIO, + { 0 }, + B_MPEG_FORMAT_FAMILY, + 100, + { 0 }, + "video/mpeg", + "MPEG (Motion Picture Experts Group)", + "mpg", + "mpg", + { 0 } + }, }; const size_t gMuxerCount = sizeof(gMuxerTable) / sizeof(media_file_format); diff --git a/src/add-ons/media/plugins/ffmpeg/config.h b/src/add-ons/media/plugins/ffmpeg/config.h index 0fe0ab4a50..aa54a933c8 100644 --- a/src/add-ons/media/plugins/ffmpeg/config.h +++ b/src/add-ons/media/plugins/ffmpeg/config.h @@ -673,14 +673,14 @@ #define CONFIG_MP2_MUXER 0 #define CONFIG_MP3_MUXER 0 #define CONFIG_MP4_MUXER 0 -#define CONFIG_MPEG1SYSTEM_MUXER 0 +#define CONFIG_MPEG1SYSTEM_MUXER 1 #define CONFIG_MPEG1VCD_MUXER 0 -#define CONFIG_MPEG1VIDEO_MUXER 0 +#define CONFIG_MPEG1VIDEO_MUXER 1 #define CONFIG_MPEG2DVD_MUXER 0 #define CONFIG_MPEG2SVCD_MUXER 0 -#define CONFIG_MPEG2VIDEO_MUXER 0 +#define CONFIG_MPEG2VIDEO_MUXER 1 #define CONFIG_MPEG2VOB_MUXER 0 -#define CONFIG_MPEGTS_MUXER 0 +#define CONFIG_MPEGTS_MUXER 1 #define CONFIG_MPJPEG_MUXER 0 #define CONFIG_MXF_MUXER 0 #define CONFIG_MXF_D10_MUXER 0 diff --git a/src/add-ons/media/plugins/ffmpeg/libavformat/utils.c b/src/add-ons/media/plugins/ffmpeg/libavformat/utils.c index 0ffe96a00b..fee6538d60 100644 --- a/src/add-ons/media/plugins/ffmpeg/libavformat/utils.c +++ b/src/add-ons/media/plugins/ffmpeg/libavformat/utils.c @@ -2723,7 +2723,10 @@ int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt){ AVPacket opkt; int ret= av_interleave_packet(s, &opkt, pkt, 0); if(ret<=0) //FIXME cleanup needed for ret<0 ? +{ +av_log(NULL, AV_LOG_ERROR, "av_interleaved_write_frame() - av_interleave_packet() failed.\n"); return ret; +} ret= s->oformat->write_packet(s, &opkt); @@ -2731,9 +2734,15 @@ int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt){ pkt= NULL; if(ret<0) +{ +av_log(NULL, AV_LOG_ERROR, "av_interleaved_write_frame() - write_packet() failed.\n"); return ret; +} if(url_ferror(s->pb)) +{ +av_log(NULL, AV_LOG_ERROR, "av_interleaved_write_frame() - url_ferror() failed.\n"); return url_ferror(s->pb); +} } } diff --git a/src/add-ons/media/plugins/ffmpeg/libswscale/swscale.h b/src/add-ons/media/plugins/ffmpeg/libswscale/swscale.h index 124a623338..6efd90fcda 100644 --- a/src/add-ons/media/plugins/ffmpeg/libswscale/swscale.h +++ b/src/add-ons/media/plugins/ffmpeg/libswscale/swscale.h @@ -28,7 +28,6 @@ */ #include "libavutil/avutil.h" -#include "libavutil/internal.h" #define LIBSWSCALE_VERSION_MAJOR 0 #define LIBSWSCALE_VERSION_MINOR 7 diff --git a/src/kits/media/MediaFile.cpp b/src/kits/media/MediaFile.cpp index 40fe43b899..dd86677eba 100644 --- a/src/kits/media/MediaFile.cpp +++ b/src/kits/media/MediaFile.cpp @@ -60,7 +60,8 @@ BMediaFile::BMediaFile(const entry_ref* ref, const media_file_format* mfi, CALLED(); _Init(); fDeleteSource = true; - _InitWriter(new(std::nothrow) BFile(ref, O_WRONLY), mfi, flags); + _InitWriter(new(std::nothrow) BFile(ref, B_CREATE_FILE | B_ERASE_FILE + | B_WRITE_ONLY), mfi, flags); } diff --git a/src/kits/media/MediaWriter.cpp b/src/kits/media/MediaWriter.cpp index 3f5bcb16e7..2c3b580ed3 100644 --- a/src/kits/media/MediaWriter.cpp +++ b/src/kits/media/MediaWriter.cpp @@ -110,7 +110,7 @@ MediaWriter::CreateEncoder(Encoder** _encoder, } StreamInfo info; - ret = fWriter->AllocateCookie(&info.cookie, format); + ret = fWriter->AllocateCookie(&info.cookie, format, codecInfo); if (ret != B_OK) { _plugin_manager.DestroyEncoder(encoder); return ret;