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;