Changeset View
Changeset View
Standalone View
Standalone View
source/blender/blenkernel/intern/writeffmpeg.c
| Show First 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | |||||
| # include "BKE_writeffmpeg.h" | # include "BKE_writeffmpeg.h" | ||||
| # include "IMB_imbuf.h" | # include "IMB_imbuf.h" | ||||
| /* This needs to be included after BLI_math_base.h otherwise it will redefine some math defines | /* This needs to be included after BLI_math_base.h otherwise it will redefine some math defines | ||||
| * like M_SQRT1_2 leading to warnings with MSVC */ | * like M_SQRT1_2 leading to warnings with MSVC */ | ||||
| # include <libavformat/avformat.h> | # include <libavformat/avformat.h> | ||||
| # include <libavcodec/avcodec.h> | # include <libavcodec/avcodec.h> | ||||
| # include <libavutil/imgutils.h> | |||||
| # include <libavutil/rational.h> | # include <libavutil/rational.h> | ||||
| # include <libavutil/samplefmt.h> | # include <libavutil/samplefmt.h> | ||||
| # include <libswscale/swscale.h> | # include <libswscale/swscale.h> | ||||
| # include "ffmpeg_compat.h" | # include "ffmpeg_compat.h" | ||||
| struct StampData; | struct StampData; | ||||
| Show All 10 Lines | typedef struct FFMpegContext { | ||||
| bool ffmpeg_preview; | bool ffmpeg_preview; | ||||
| int ffmpeg_crf; /* set to 0 to not use CRF mode; we have another flag for lossless anyway. */ | int ffmpeg_crf; /* set to 0 to not use CRF mode; we have another flag for lossless anyway. */ | ||||
| int ffmpeg_preset; /* see eFFMpegPreset */ | int ffmpeg_preset; /* see eFFMpegPreset */ | ||||
| AVFormatContext *outfile; | AVFormatContext *outfile; | ||||
| AVStream *video_stream; | AVStream *video_stream; | ||||
| AVStream *audio_stream; | AVStream *audio_stream; | ||||
| AVFrame *current_frame; | AVFrame *current_frame; /* Image frame in output pixel format. */ | ||||
| /* Image frame in Blender's own pixel format, may need conversion to the output pixel format. */ | |||||
| AVFrame *img_convert_frame; | |||||
| struct SwsContext *img_convert_ctx; | struct SwsContext *img_convert_ctx; | ||||
| uint8_t *audio_input_buffer; | uint8_t *audio_input_buffer; | ||||
| uint8_t *audio_deinterleave_buffer; | uint8_t *audio_deinterleave_buffer; | ||||
| int audio_input_samples; | int audio_input_samples; | ||||
| # ifndef FFMPEG_HAVE_ENCODE_AUDIO2 | # ifndef FFMPEG_HAVE_ENCODE_AUDIO2 | ||||
| uint8_t *audio_output_buffer; | uint8_t *audio_output_buffer; | ||||
| int audio_outbuf_size; | int audio_outbuf_size; | ||||
| ▲ Show 20 Lines • Show All 167 Lines • ▼ Show 20 Lines | static AVFrame *alloc_picture(int pix_fmt, int width, int height) | ||||
| size = avpicture_get_size(pix_fmt, width, height); | size = avpicture_get_size(pix_fmt, width, height); | ||||
| /* allocate the actual picture buffer */ | /* allocate the actual picture buffer */ | ||||
| buf = MEM_mallocN(size, "AVFrame buffer"); | buf = MEM_mallocN(size, "AVFrame buffer"); | ||||
| if (!buf) { | if (!buf) { | ||||
| free(f); | free(f); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| avpicture_fill((AVPicture *)f, buf, pix_fmt, width, height); | avpicture_fill((AVPicture *)f, buf, pix_fmt, width, height); | ||||
| f->format = pix_fmt; | |||||
| f->width = width; | |||||
| f->height = height; | |||||
| return f; | return f; | ||||
| } | } | ||||
| /* Get the correct file extensions for the requested format, | /* Get the correct file extensions for the requested format, | ||||
| * first is always desired guess_format parameter */ | * first is always desired guess_format parameter */ | ||||
| static const char **get_file_extensions(int format) | static const char **get_file_extensions(int format) | ||||
| { | { | ||||
| switch (format) { | switch (format) { | ||||
| ▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | static int write_video_frame( | ||||
| if (!success) { | if (!success) { | ||||
| BKE_report(reports, RPT_ERROR, "Error writing frame"); | BKE_report(reports, RPT_ERROR, "Error writing frame"); | ||||
| } | } | ||||
| return success; | return success; | ||||
| } | } | ||||
| /* read and encode a frame of audio from the buffer */ | /* read and encode a frame of audio from the buffer */ | ||||
| static AVFrame *generate_video_frame(FFMpegContext *context, uint8_t *pixels, ReportList *reports) | static AVFrame *generate_video_frame(FFMpegContext *context, | ||||
| const uint8_t *pixels, | |||||
| ReportList *reports) | |||||
| { | { | ||||
| uint8_t *rendered_frame; | |||||
| AVCodecContext *c = context->video_stream->codec; | AVCodecContext *c = context->video_stream->codec; | ||||
| int width = c->width; | |||||
| int height = c->height; | int height = c->height; | ||||
| AVFrame *rgb_frame; | AVFrame *rgb_frame; | ||||
| if (c->pix_fmt != AV_PIX_FMT_BGR32) { | if (context->img_convert_frame != NULL) { | ||||
| rgb_frame = alloc_picture(AV_PIX_FMT_BGR32, width, height); | /* Pixel format conversion is needed. */ | ||||
| if (!rgb_frame) { | rgb_frame = context->img_convert_frame; | ||||
| BKE_report(reports, RPT_ERROR, "Could not allocate temporary frame"); | |||||
| return NULL; | |||||
| } | |||||
| } | } | ||||
| else { | else { | ||||
| /* The output pixel format is Blender's internal pixel format. */ | |||||
| rgb_frame = context->current_frame; | rgb_frame = context->current_frame; | ||||
| } | } | ||||
| rendered_frame = pixels; | /* Copy the Blender pixels into the FFmpeg datastructure, taking care of endianness and flipping | ||||
| * the image vertically. */ | |||||
| /* Do RGBA-conversion and flipping in one step depending | int linesize = rgb_frame->linesize[0]; | ||||
| * on CPU-Endianess */ | for (int y = 0; y < height; y++) { | ||||
| uint8_t *target = rgb_frame->data[0] + linesize * (height - y - 1); | |||||
| const uint8_t *src = pixels + linesize * y; | |||||
| if (ENDIAN_ORDER == L_ENDIAN) { | # if ENDIAN_ORDER == L_ENDIAN | ||||
| int y; | memcpy(target, src, linesize); | ||||
| for (y = 0; y < height; y++) { | |||||
| uint8_t *target = rgb_frame->data[0] + width * 4 * (height - y - 1); | |||||
| uint8_t *src = rendered_frame + width * 4 * y; | |||||
| uint8_t *end = src + width * 4; | |||||
| while (src != end) { | |||||
| target[3] = src[3]; | |||||
| target[2] = src[2]; | |||||
| target[1] = src[1]; | |||||
| target[0] = src[0]; | |||||
| target += 4; | # elif ENDIAN_ORDER == B_ENDIAN | ||||
| src += 4; | const uint8_t *end = src + linesize; | ||||
| } | |||||
| } | |||||
| } | |||||
| else { | |||||
| int y; | |||||
| for (y = 0; y < height; y++) { | |||||
| uint8_t *target = rgb_frame->data[0] + width * 4 * (height - y - 1); | |||||
| uint8_t *src = rendered_frame + width * 4 * y; | |||||
| uint8_t *end = src + width * 4; | |||||
| while (src != end) { | while (src != end) { | ||||
| target[3] = src[0]; | target[3] = src[0]; | ||||
| target[2] = src[1]; | target[2] = src[1]; | ||||
| target[1] = src[2]; | target[1] = src[2]; | ||||
| target[0] = src[3]; | target[0] = src[3]; | ||||
| target += 4; | target += 4; | ||||
| src += 4; | src += 4; | ||||
| } | } | ||||
| } | # else | ||||
| # error ENDIAN_ORDER should either be L_ENDIAN or B_ENDIAN. | |||||
| # endif | |||||
| } | } | ||||
| if (c->pix_fmt != AV_PIX_FMT_BGR32) { | /* Convert to the output pixel format, if it's different that Blender's internal one. */ | ||||
| if (context->img_convert_frame != NULL) { | |||||
| BLI_assert(context->img_convert_ctx != NULL); | |||||
| sws_scale(context->img_convert_ctx, | sws_scale(context->img_convert_ctx, | ||||
| (const uint8_t *const *)rgb_frame->data, | (const uint8_t *const *)rgb_frame->data, | ||||
| rgb_frame->linesize, | rgb_frame->linesize, | ||||
| 0, | 0, | ||||
| c->height, | c->height, | ||||
| context->current_frame->data, | context->current_frame->data, | ||||
| context->current_frame->linesize); | context->current_frame->linesize); | ||||
| delete_picture(rgb_frame); | |||||
| } | } | ||||
| context->current_frame->format = AV_PIX_FMT_BGR32; | |||||
| context->current_frame->width = width; | |||||
| context->current_frame->height = height; | |||||
| return context->current_frame; | return context->current_frame; | ||||
| } | } | ||||
| static void set_ffmpeg_property_option(AVCodecContext *c, | static void set_ffmpeg_property_option(AVCodecContext *c, | ||||
| IDProperty *prop, | IDProperty *prop, | ||||
| AVDictionary **dictionary) | AVDictionary **dictionary) | ||||
| { | { | ||||
| char name[128]; | char name[128]; | ||||
| ▲ Show 20 Lines • Show All 234 Lines • ▼ Show 20 Lines | if (codec_id == AV_CODEC_ID_HUFFYUV) { | ||||
| } | } | ||||
| } | } | ||||
| if (codec_id == AV_CODEC_ID_FFV1) { | if (codec_id == AV_CODEC_ID_FFV1) { | ||||
| c->pix_fmt = AV_PIX_FMT_RGB32; | c->pix_fmt = AV_PIX_FMT_RGB32; | ||||
| } | } | ||||
| if (codec_id == AV_CODEC_ID_QTRLE) { | if (codec_id == AV_CODEC_ID_QTRLE) { | ||||
| /* Always write to ARGB. The default pixel format of QTRLE is RGB24, which uses 3 bytes per | if (rd->im_format.planes == R_IMF_PLANES_RGBA) { | ||||
| * pixels, which breaks the export. */ | |||||
| c->pix_fmt = AV_PIX_FMT_ARGB; | c->pix_fmt = AV_PIX_FMT_ARGB; | ||||
| } | } | ||||
| } | |||||
| if (codec_id == AV_CODEC_ID_PNG) { | if (codec_id == AV_CODEC_ID_PNG) { | ||||
| if (rd->im_format.planes == R_IMF_PLANES_RGBA) { | if (rd->im_format.planes == R_IMF_PLANES_RGBA) { | ||||
| c->pix_fmt = AV_PIX_FMT_RGBA; | c->pix_fmt = AV_PIX_FMT_RGBA; | ||||
| } | } | ||||
| } | } | ||||
| if ((of->oformat->flags & AVFMT_GLOBALHEADER)) { | if ((of->oformat->flags & AVFMT_GLOBALHEADER)) { | ||||
| Show All 11 Lines | static AVStream *alloc_video_stream(FFMpegContext *context, | ||||
| if (avcodec_open2(c, codec, &opts) < 0) { | if (avcodec_open2(c, codec, &opts) < 0) { | ||||
| BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); | BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size); | ||||
| av_dict_free(&opts); | av_dict_free(&opts); | ||||
| return NULL; | return NULL; | ||||
| } | } | ||||
| av_dict_free(&opts); | av_dict_free(&opts); | ||||
| /* FFmpeg expects its data in the output pixel format. */ | |||||
| context->current_frame = alloc_picture(c->pix_fmt, c->width, c->height); | context->current_frame = alloc_picture(c->pix_fmt, c->width, c->height); | ||||
| if (c->pix_fmt == AV_PIX_FMT_RGBA) { | |||||
| /* Output pixel format is the same we use internally, no conversion necessary. */ | |||||
| context->img_convert_frame = NULL; | |||||
| context->img_convert_ctx = NULL; | |||||
| } | |||||
| else { | |||||
| /* Output pixel format is different, allocate frame for conversion. */ | |||||
| context->img_convert_frame = alloc_picture(AV_PIX_FMT_RGBA, c->width, c->height); | |||||
| context->img_convert_ctx = sws_getContext(c->width, | context->img_convert_ctx = sws_getContext(c->width, | ||||
| c->height, | c->height, | ||||
| AV_PIX_FMT_BGR32, | AV_PIX_FMT_RGBA, | ||||
| c->width, | c->width, | ||||
| c->height, | c->height, | ||||
| c->pix_fmt, | c->pix_fmt, | ||||
| SWS_BICUBIC, | SWS_BICUBIC, | ||||
| NULL, | NULL, | ||||
| NULL, | NULL, | ||||
| NULL); | NULL); | ||||
| } | |||||
| return st; | return st; | ||||
| } | } | ||||
| static AVStream *alloc_audio_stream(FFMpegContext *context, | static AVStream *alloc_audio_stream(FFMpegContext *context, | ||||
| RenderData *rd, | RenderData *rd, | ||||
| int codec_id, | int codec_id, | ||||
| AVFormatContext *of, | AVFormatContext *of, | ||||
| char *error, | char *error, | ||||
| ▲ Show 20 Lines • Show All 672 Lines • ▼ Show 20 Lines | if (context->audio_stream != NULL && context->audio_stream->codec != NULL) { | ||||
| context->audio_stream = NULL; | context->audio_stream = NULL; | ||||
| } | } | ||||
| /* free the temp buffer */ | /* free the temp buffer */ | ||||
| if (context->current_frame != NULL) { | if (context->current_frame != NULL) { | ||||
| delete_picture(context->current_frame); | delete_picture(context->current_frame); | ||||
| context->current_frame = NULL; | context->current_frame = NULL; | ||||
| } | } | ||||
| if (context->img_convert_frame != NULL) { | |||||
| delete_picture(context->img_convert_frame); | |||||
| context->img_convert_frame = NULL; | |||||
| } | |||||
| if (context->outfile != NULL && context->outfile->oformat) { | if (context->outfile != NULL && context->outfile->oformat) { | ||||
| if (!(context->outfile->oformat->flags & AVFMT_NOFILE)) { | if (!(context->outfile->oformat->flags & AVFMT_NOFILE)) { | ||||
| avio_close(context->outfile->pb); | avio_close(context->outfile->pb); | ||||
| } | } | ||||
| } | } | ||||
| if (context->outfile != NULL) { | if (context->outfile != NULL) { | ||||
| avformat_free_context(context->outfile); | avformat_free_context(context->outfile); | ||||
| context->outfile = NULL; | context->outfile = NULL; | ||||
| ▲ Show 20 Lines • Show All 455 Lines • Show Last 20 Lines | |||||