Permalink
Cannot retrieve contributors at this time
| /* | |
| * This file is part of cyanrip. | |
| * | |
| * cyanrip is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * cyanrip is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with cyanrip; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| */ | |
| #include <time.h> | |
| #include <stdarg.h> | |
| #include <stdatomic.h> | |
| #include <strings.h> | |
| #include <libavcodec/avcodec.h> | |
| #include <libavformat/avformat.h> | |
| #include <libswresample/swresample.h> | |
| #include <libavfilter/buffersink.h> | |
| #include <libavfilter/buffersrc.h> | |
| #include <libavutil/opt.h> | |
| #include "fifo_frame.h" | |
| #include "cyanrip_encode.h" | |
| #include "cyanrip_log.h" | |
| #include "os_compat.h" | |
| struct cyanrip_enc_ctx { | |
| cyanrip_ctx *ctx; | |
| AVBufferRef *fifo; | |
| pthread_t thread; | |
| AVFormatContext *avf; | |
| SwrContext *swr; | |
| AVCodecContext *out_avctx; | |
| atomic_int status; | |
| int audio_stream_index; | |
| }; | |
| struct cyanrip_dec_ctx { | |
| /* HDCD decoding */ | |
| AVFilterGraph *graph; | |
| AVFilterContext *buffersink_ctx; | |
| AVFilterContext *buffersrc_ctx; | |
| AVFilterGraph *filter_graph; | |
| }; | |
| void cyanrip_print_codecs(void) | |
| { | |
| for (int i = 0; i < CYANRIP_FORMATS_NB; i++) { | |
| const cyanrip_out_fmt *cfmt = &crip_fmt_info[i]; | |
| if (avcodec_find_encoder(cfmt->codec) || | |
| ((cfmt->codec == AV_CODEC_ID_NONE) && | |
| avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE) && | |
| avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE))) { | |
| const char *str = cfmt->coverart_supported ? "\t(supports cover art)" : ""; | |
| cyanrip_log(NULL, 0, "\t%s\tfolder: [%s]\textension: %s%s\n", cfmt->name, cfmt->folder_suffix, cfmt->ext, str); | |
| } | |
| } | |
| } | |
| int cyanrip_validate_fmt(const char *fmt) | |
| { | |
| for (int i = 0; i < CYANRIP_FORMATS_NB; i++) { | |
| const cyanrip_out_fmt *cfmt = &crip_fmt_info[i]; | |
| if ((!strncasecmp(fmt, cfmt->name, strlen(fmt))) && | |
| (strlen(fmt) == strlen(cfmt->name))) { | |
| if (cfmt->codec == AV_CODEC_ID_NONE && | |
| avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE) && | |
| avcodec_find_encoder(AV_CODEC_ID_PCM_S32LE)) | |
| return i; | |
| else if (avcodec_find_encoder(cfmt->codec)) | |
| return i; | |
| cyanrip_log(NULL, 0, "Encoder for %s not compiled in ffmpeg!\n", cfmt->name); | |
| return -1; | |
| } | |
| } | |
| return -1; | |
| } | |
| const char *cyanrip_fmt_desc(enum cyanrip_output_formats format) | |
| { | |
| return format < CYANRIP_FORMATS_NB ? crip_fmt_info[format].name : NULL; | |
| } | |
| const char *cyanrip_fmt_folder(enum cyanrip_output_formats format) | |
| { | |
| return format < CYANRIP_FORMATS_NB ? crip_fmt_info[format].folder_suffix : NULL; | |
| } | |
| static const uint64_t pick_codec_channel_layout(AVCodec *codec) | |
| { | |
| int i = 0; | |
| int max_channels = 0; | |
| uint64_t ilayout = AV_CH_LAYOUT_STEREO; | |
| int in_channels = av_get_channel_layout_nb_channels(ilayout); | |
| uint64_t best_layout = 0; | |
| /* Supports anything */ | |
| if (!codec->channel_layouts) | |
| return ilayout; | |
| /* Try to match */ | |
| while (1) { | |
| if (!codec->channel_layouts[i]) | |
| break; | |
| if (codec->channel_layouts[i] == ilayout) | |
| return codec->channel_layouts[i]; | |
| i++; | |
| } | |
| i = 0; | |
| /* Try to match channel counts */ | |
| while (1) { | |
| if (!codec->channel_layouts[i]) | |
| break; | |
| int num = av_get_channel_layout_nb_channels(codec->channel_layouts[i]); | |
| if (num > max_channels) { | |
| max_channels = num; | |
| best_layout = codec->channel_layouts[i]; | |
| } | |
| if (num >= in_channels) | |
| return codec->channel_layouts[i]; | |
| i++; | |
| } | |
| /* Whatever */ | |
| return best_layout; | |
| } | |
| static enum AVSampleFormat pick_codec_sample_fmt(AVCodec *codec, int hdcd) | |
| { | |
| int i = 0; | |
| int max_bps = 0; | |
| int ibps = hdcd ? 20 : 16; | |
| enum AVSampleFormat ifmt = hdcd ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16; | |
| enum AVSampleFormat max_bps_fmt = AV_SAMPLE_FMT_NONE; | |
| ibps = ibps >> 3; | |
| /* Accepts anything */ | |
| if (!codec->sample_fmts) | |
| return ifmt; | |
| /* Try to match the input sample format first */ | |
| while (1) { | |
| if (codec->sample_fmts[i] == -1) | |
| break; | |
| if (codec->sample_fmts[i] == ifmt) | |
| return codec->sample_fmts[i]; | |
| i++; | |
| } | |
| i = 0; | |
| /* Try to match bits per sample */ | |
| while (1) { | |
| if (codec->sample_fmts[i] == -1) | |
| break; | |
| int bps = av_get_bytes_per_sample(codec->sample_fmts[i]); | |
| if (bps > max_bps) { | |
| max_bps = bps; | |
| max_bps_fmt = codec->sample_fmts[i]; | |
| } | |
| if (bps >= ibps) | |
| return codec->sample_fmts[i]; | |
| i++; | |
| } | |
| /* Return the best one */ | |
| return max_bps_fmt; | |
| } | |
| static int pick_codec_sample_rate(AVCodec *codec) | |
| { | |
| int i = 0, ret; | |
| int irate = 44100; | |
| if (!codec->supported_samplerates) | |
| return irate; | |
| /* Go to the array terminator (0) */ | |
| while (codec->supported_samplerates[++i] > 0); | |
| /* Alloc, copy and sort array upwards */ | |
| int *tmp = av_malloc(i*sizeof(int)); | |
| memcpy(tmp, codec->supported_samplerates, i*sizeof(int)); | |
| qsort(tmp, i, sizeof(int), cmp_numbers); | |
| /* Pick lowest one above the input rate, otherwise just use the highest one */ | |
| for (int j = 0; j < i; j++) { | |
| ret = tmp[j]; | |
| if (ret >= irate) | |
| break; | |
| } | |
| av_free(tmp); | |
| return ret; | |
| } | |
| static AVCodecContext *setup_out_avctx(cyanrip_ctx *ctx, AVFormatContext *avf, | |
| AVCodec *codec, const cyanrip_out_fmt *cfmt) | |
| { | |
| AVCodecContext *avctx = avcodec_alloc_context3(codec); | |
| if (!avctx) | |
| return NULL; | |
| avctx->opaque = ctx; | |
| avctx->bit_rate = cfmt->lossless ? 0 : lrintf(ctx->settings.bitrate*1000.0f); | |
| avctx->sample_fmt = pick_codec_sample_fmt(codec, ctx->settings.decode_hdcd); | |
| avctx->channel_layout = pick_codec_channel_layout(codec); | |
| avctx->compression_level = cfmt->compression_level; | |
| avctx->sample_rate = pick_codec_sample_rate(codec); | |
| avctx->time_base = (AVRational){ 1, avctx->sample_rate }; | |
| avctx->channels = av_get_channel_layout_nb_channels(avctx->channel_layout); | |
| if (cfmt->lossless && ctx->settings.decode_hdcd) | |
| avctx->bits_per_raw_sample = 24; | |
| else if (cfmt->lossless) | |
| avctx->bits_per_raw_sample = 16; | |
| if (avf->oformat->flags & AVFMT_GLOBALHEADER) | |
| avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; | |
| return avctx; | |
| } | |
| void cyanrip_free_dec_ctx(cyanrip_ctx *ctx, cyanrip_dec_ctx **s) | |
| { | |
| if (!s || !*s) | |
| return; | |
| cyanrip_dec_ctx *dec_ctx = *s; | |
| if (dec_ctx->graph) { | |
| cyanrip_set_av_log_capture(ctx, 1, 1, AV_LOG_INFO); | |
| avfilter_graph_free(&dec_ctx->graph); | |
| cyanrip_set_av_log_capture(ctx, 0, 0, 0); | |
| } | |
| av_freep(s); | |
| } | |
| static int init_hdcd_decoding(cyanrip_ctx *ctx, cyanrip_dec_ctx *s) | |
| { | |
| int ret = 0; | |
| AVFilterInOut *outputs = NULL; | |
| AVFilterInOut *inputs = NULL; | |
| s->graph = avfilter_graph_alloc(); | |
| if (!s->graph) | |
| return AVERROR(ENOMEM); | |
| const AVFilter *abuffersrc = avfilter_get_by_name("abuffer"); | |
| char args[512]; | |
| uint64_t layout = AV_CH_LAYOUT_STEREO; | |
| snprintf(args, sizeof(args), | |
| "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%"PRIx64, | |
| 1, 44100, 44100, av_get_sample_fmt_name(AV_SAMPLE_FMT_S16), layout); | |
| ret = avfilter_graph_create_filter(&s->buffersrc_ctx, abuffersrc, "in", | |
| args, NULL, s->graph); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error creating filter source: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| const AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); | |
| ret = avfilter_graph_create_filter(&s->buffersink_ctx, abuffersink, "out", | |
| NULL, NULL, s->graph); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error creating filter sink: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S32, -1 }; | |
| ret = av_opt_set_int_list(s->buffersink_ctx, "sample_fmts", out_sample_fmts, -1, | |
| AV_OPT_SEARCH_CHILDREN); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error setting filter sample format: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| static const int64_t out_channel_layouts[] = { AV_CH_LAYOUT_STEREO, -1 }; | |
| ret = av_opt_set_int_list(s->buffersink_ctx, "channel_layouts", out_channel_layouts, -1, | |
| AV_OPT_SEARCH_CHILDREN); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error setting filter channel layout: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| static const int out_sample_rates[] = { 44100, -1 }; | |
| ret = av_opt_set_int_list(s->buffersink_ctx, "sample_rates", out_sample_rates, -1, | |
| AV_OPT_SEARCH_CHILDREN); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error setting filter sample rate: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| outputs = avfilter_inout_alloc(); | |
| if (!outputs) { | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| outputs->name = av_strdup("in"); | |
| outputs->filter_ctx = s->buffersrc_ctx; | |
| outputs->pad_idx = 0; | |
| outputs->next = NULL; | |
| inputs = avfilter_inout_alloc(); | |
| if (!inputs) { | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| inputs->name = av_strdup("out"); | |
| inputs->filter_ctx = s->buffersink_ctx; | |
| inputs->pad_idx = 0; | |
| inputs->next = NULL; | |
| const char *filter_desc = "hdcd"; | |
| ret = avfilter_graph_parse_ptr(s->graph, filter_desc, &inputs, &outputs, NULL); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error parsing filter graph: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| ret = avfilter_graph_config(s->graph, NULL); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error configuring filter graph: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| avfilter_inout_free(&inputs); | |
| avfilter_inout_free(&outputs); | |
| return 0; | |
| fail: | |
| avfilter_inout_free(&inputs); | |
| avfilter_inout_free(&outputs); | |
| return ret; | |
| } | |
| int cyanrip_create_dec_ctx(cyanrip_ctx *ctx, cyanrip_dec_ctx **s, | |
| cyanrip_track *t) | |
| { | |
| int ret = 0; | |
| cyanrip_dec_ctx *dec_ctx = av_mallocz(sizeof(*dec_ctx)); | |
| if (!dec_ctx) | |
| return AVERROR(ENOMEM); | |
| if (ctx->settings.decode_hdcd) { | |
| ret = init_hdcd_decoding(ctx, dec_ctx); | |
| if (ret < 0) | |
| goto fail; | |
| } | |
| *s = dec_ctx; | |
| return 0; | |
| fail: | |
| cyanrip_free_dec_ctx(ctx, &dec_ctx); | |
| return ret; | |
| } | |
| static int push_frame_to_encs(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, | |
| int num_enc, AVFrame *frame) | |
| { | |
| int ret; | |
| for (int i = 0; i < num_enc; i++) { | |
| int status = atomic_load(&enc_ctx[i]->status); | |
| if (status < 0) | |
| return status; | |
| ret = cr_frame_fifo_push(enc_ctx[i]->fifo, frame); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error pushing frame to FIFO: %s!\n", av_err2str(ret)); | |
| return ret; | |
| } | |
| } | |
| return 0; | |
| } | |
| static int filter_frame(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, | |
| int num_enc, cyanrip_dec_ctx *dec_ctx, AVFrame *frame) | |
| { | |
| int ret = 0; | |
| AVFrame *dec_frame = NULL; | |
| if (!dec_ctx->buffersrc_ctx) | |
| return push_frame_to_encs(ctx, enc_ctx, num_enc, frame); | |
| ret = av_buffersrc_add_frame_flags(dec_ctx->buffersrc_ctx, frame, | |
| AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT | | |
| AV_BUFFERSRC_FLAG_KEEP_REF); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| while (1) { | |
| dec_frame = av_frame_alloc(); | |
| if (!dec_frame) { | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| ret = av_buffersink_get_frame(dec_ctx->buffersink_ctx, dec_frame); | |
| if (ret == AVERROR(EAGAIN)) { | |
| av_frame_free(&dec_frame); | |
| ret = 0; | |
| break; | |
| } else if (ret == AVERROR_EOF) { | |
| av_frame_free(&dec_frame); | |
| ret = 0; | |
| return push_frame_to_encs(ctx, enc_ctx, num_enc, NULL); | |
| } else if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error filtering frame: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| push_frame_to_encs(ctx, enc_ctx, num_enc, dec_frame); | |
| av_frame_free(&dec_frame); | |
| } | |
| fail: | |
| return ret; | |
| } | |
| int cyanrip_send_pcm_to_encoders(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, | |
| int num_enc, cyanrip_dec_ctx *dec_ctx, | |
| const uint8_t *data, int bytes) | |
| { | |
| int ret = 0; | |
| AVFrame *frame = NULL; | |
| if (!data && !bytes) | |
| goto send; | |
| else if (!bytes) | |
| return 0; | |
| frame = av_frame_alloc(); | |
| if (!frame) { | |
| cyanrip_log(ctx, 0, "Error allocating frame!\n"); | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| frame->sample_rate = 44100; | |
| frame->nb_samples = bytes >> 2; | |
| frame->channel_layout = AV_CH_LAYOUT_STEREO; | |
| frame->format = AV_SAMPLE_FMT_S16; | |
| ret = av_frame_get_buffer(frame, 0); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Error allocating frame: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| memcpy(frame->data[0], data, bytes); | |
| send: | |
| ret = filter_frame(ctx, enc_ctx, num_enc, dec_ctx, frame); | |
| fail: | |
| av_frame_free(&frame); | |
| return ret; | |
| } | |
| static SwrContext *setup_init_swr(cyanrip_ctx *ctx, AVCodecContext *out_avctx, int hdcd) | |
| { | |
| SwrContext *swr = swr_alloc(); | |
| if (!swr) { | |
| cyanrip_log(ctx, 0, "Could not alloc swr context!\n"); | |
| return NULL; | |
| } | |
| enum AVSampleFormat in_sample_fmt = hdcd ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16; | |
| av_opt_set_int (swr, "in_sample_rate", 44100, 0); | |
| av_opt_set_channel_layout(swr, "in_channel_layout", AV_CH_LAYOUT_STEREO, 0); | |
| av_opt_set_sample_fmt (swr, "in_sample_fmt", in_sample_fmt, 0); | |
| av_opt_set_int (swr, "out_sample_rate", out_avctx->sample_rate, 0); | |
| av_opt_set_channel_layout(swr, "out_channel_layout", out_avctx->channel_layout, 0); | |
| av_opt_set_sample_fmt (swr, "out_sample_fmt", out_avctx->sample_fmt, 0); | |
| if (swr_init(swr) < 0) { | |
| cyanrip_log(ctx, 0, "Could not init swr context!\n"); | |
| swr_free(&swr); | |
| return NULL; | |
| } | |
| return swr; | |
| } | |
| static int64_t get_next_audio_pts(cyanrip_enc_ctx *ctx, AVFrame *in) | |
| { | |
| const int64_t m = (int64_t)44100 * ctx->out_avctx->sample_rate; | |
| const int64_t b = (int64_t)ctx->out_avctx->time_base.num * m; | |
| const int64_t c = ctx->out_avctx->time_base.den; | |
| const int64_t in_pts = in ? in->pts : AV_NOPTS_VALUE; | |
| int64_t npts = in_pts == AV_NOPTS_VALUE ? AV_NOPTS_VALUE : av_rescale(in_pts, b, c); | |
| npts = swr_next_pts(ctx->swr, npts); | |
| int64_t out_pts = av_rescale(npts, c, b); | |
| return out_pts; | |
| } | |
| static int audio_process_frame(cyanrip_enc_ctx *ctx, AVFrame **input, int flush) | |
| { | |
| int ret; | |
| int frame_size = ctx->out_avctx->frame_size; | |
| int64_t resampled_frame_pts = get_next_audio_pts(ctx, *input); | |
| /* Resample the frame, can be NULL */ | |
| ret = swr_convert_frame(ctx->swr, NULL, *input); | |
| av_frame_free(input); | |
| if (ret < 0) { | |
| av_log(ctx, AV_LOG_ERROR, "Error pushing audio for resampling: %s!\n", av_err2str(ret)); | |
| return ret; | |
| } | |
| /* Not enough output samples to get a frame */ | |
| if (!flush) { | |
| int out_samples = swr_get_out_samples(ctx->swr, 0); | |
| if (!frame_size && out_samples) | |
| frame_size = out_samples; | |
| else if (!out_samples || ((frame_size) && (out_samples < frame_size))) | |
| return AVERROR(EAGAIN); | |
| } else if (!frame_size && ctx->swr) { | |
| frame_size = swr_get_out_samples(ctx->swr, 0); | |
| if (!frame_size) | |
| return 0; | |
| } | |
| if (flush && !ctx->swr) | |
| return 0; | |
| AVFrame *out_frame = NULL; | |
| if (!(out_frame = av_frame_alloc())) { | |
| av_log(ctx, AV_LOG_ERROR, "Error allocating frame!\n"); | |
| return AVERROR(ENOMEM); | |
| } | |
| out_frame->format = ctx->out_avctx->sample_fmt; | |
| out_frame->channel_layout = ctx->out_avctx->channel_layout; | |
| out_frame->sample_rate = ctx->out_avctx->sample_rate; | |
| out_frame->pts = resampled_frame_pts; | |
| /* SWR sets this field to whatever it can output if it can't this much */ | |
| out_frame->nb_samples = frame_size; | |
| /* Get frame buffer */ | |
| ret = av_frame_get_buffer(out_frame, 0); | |
| if (ret < 0) { | |
| av_log(ctx, AV_LOG_ERROR, "Error allocating frame: %s!\n", av_err2str(ret)); | |
| av_frame_free(&out_frame); | |
| return ret; | |
| } | |
| /* Resample */ | |
| ret = swr_convert_frame(ctx->swr, out_frame, NULL); | |
| if (ret < 0) { | |
| av_log(ctx, AV_LOG_ERROR, "Error pulling resampled audio: %s!\n", av_err2str(ret)); | |
| av_frame_free(&out_frame); | |
| return ret; | |
| } | |
| /* swr has been drained */ | |
| if (!out_frame->nb_samples) { | |
| av_frame_free(&out_frame); | |
| swr_free(&ctx->swr); | |
| } | |
| *input = out_frame; | |
| return 0; | |
| } | |
| int cyanrip_end_track_encoding(cyanrip_enc_ctx **s) | |
| { | |
| cyanrip_enc_ctx *ctx; | |
| if (!s || !*s) | |
| return 0; | |
| ctx = *s; | |
| cr_frame_fifo_push(ctx->fifo, NULL); | |
| pthread_join(ctx->thread, NULL); | |
| swr_free(&ctx->swr); | |
| avcodec_close(ctx->out_avctx); | |
| if (ctx->avf) | |
| avio_closep(&ctx->avf->pb); | |
| avformat_free_context(ctx->avf); | |
| av_buffer_unref(&ctx->fifo); | |
| av_free(ctx->out_avctx); | |
| int status = ctx->status; | |
| av_freep(s); | |
| return status; | |
| } | |
| static void *cyanrip_track_encoding(void *ctx) | |
| { | |
| cyanrip_enc_ctx *s = ctx; | |
| int ret = 0, flushing = 0; | |
| while (1) { | |
| AVFrame *out_frame = NULL; | |
| if (!flushing) { | |
| out_frame = cr_frame_fifo_pop(s->fifo); | |
| flushing = !out_frame; | |
| } | |
| ret = audio_process_frame(ctx, &out_frame, flushing); | |
| if (ret == AVERROR(EAGAIN)) | |
| continue; | |
| else if (ret) | |
| goto fail; | |
| /* Give frame */ | |
| ret = avcodec_send_frame(s->out_avctx, out_frame); | |
| av_frame_free(&out_frame); | |
| if (ret < 0) { | |
| cyanrip_log(s->ctx, 0, "Error encoding: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| /* Return */ | |
| while (1) { | |
| AVPacket out_pkt; | |
| av_init_packet(&out_pkt); | |
| ret = avcodec_receive_packet(s->out_avctx, &out_pkt); | |
| if (ret == AVERROR_EOF) { | |
| ret = 0; | |
| goto write_trailer; | |
| } else if (ret == AVERROR(EAGAIN)) { | |
| ret = 0; | |
| break; | |
| } else if (ret < 0) { | |
| cyanrip_log(s->ctx, 0, "Error while encoding: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| int sid = s->audio_stream_index; | |
| out_pkt.stream_index = sid; | |
| AVRational src_tb = s->out_avctx->time_base; | |
| AVRational dst_tb = s->avf->streams[sid]->time_base; | |
| /* Rescale timestamps to container */ | |
| out_pkt.pts = av_rescale_q(out_pkt.pts, src_tb, dst_tb); | |
| out_pkt.dts = av_rescale_q(out_pkt.dts, src_tb, dst_tb); | |
| out_pkt.duration = av_rescale_q(out_pkt.duration, src_tb, dst_tb); | |
| ret = av_interleaved_write_frame(s->avf, &out_pkt); | |
| av_packet_unref(&out_pkt); | |
| if (ret < 0) { | |
| cyanrip_log(s->ctx, 0, "Error writing packet: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| } | |
| } | |
| write_trailer: | |
| if ((ret = av_write_trailer(s->avf)) < 0) { | |
| cyanrip_log(s->ctx, 0, "Error writing trailer: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| fail: | |
| atomic_store(&s->status, ret); | |
| return NULL; | |
| } | |
| int cyanrip_init_track_encoding(cyanrip_ctx *ctx, cyanrip_enc_ctx **enc_ctx, | |
| cyanrip_dec_ctx *dec_ctx, cyanrip_track *t, | |
| enum cyanrip_output_formats format) | |
| { | |
| int ret = 0; | |
| const cyanrip_out_fmt *cfmt = &crip_fmt_info[format]; | |
| cyanrip_enc_ctx *s = av_mallocz(sizeof(*s)); | |
| AVStream *st_aud = NULL; | |
| AVStream *st_img = NULL; | |
| AVCodec *out_codec = NULL; | |
| s->ctx = ctx; | |
| atomic_init(&s->status, 0); | |
| char *filename = crip_get_path(ctx, CRIP_PATH_TRACK, 1, cfmt, t); | |
| /* lavf init */ | |
| ret = avformat_alloc_output_context2(&s->avf, NULL, cfmt->lavf_name, filename); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Unable to init lavf context: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| st_aud = avformat_new_stream(s->avf, NULL); | |
| if (!st_aud) { | |
| cyanrip_log(ctx, 0, "Unable to alloc stream!\n"); | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| /* Cover image init */ | |
| CRIPArt *art = &t->art; | |
| if (!art->pkt) { | |
| int i; | |
| for (i = 0; i < ctx->nb_cover_arts; i++) | |
| if (!strcmp(dict_get(ctx->cover_arts[i].meta, "title"), "Front")) | |
| break; | |
| art = &ctx->cover_arts[i == ctx->nb_cover_arts ? 0 : i]; | |
| } | |
| if (art->pkt && cfmt->coverart_supported) { | |
| st_img = avformat_new_stream(s->avf, NULL); | |
| if (!st_img) { | |
| cyanrip_log(ctx, 0, "Unable to alloc stream!\n"); | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| memcpy(st_img->codecpar, art->params, sizeof(AVCodecParameters)); | |
| st_img->disposition |= AV_DISPOSITION_ATTACHED_PIC; | |
| st_img->time_base = (AVRational){ 1, 25 }; | |
| s->avf->oformat->video_codec = st_img->codecpar->codec_id; | |
| av_dict_copy(&st_img->metadata, art->meta, 0); | |
| } | |
| /* Find encoder */ | |
| if (cfmt->codec == AV_CODEC_ID_NONE) | |
| out_codec = avcodec_find_encoder(ctx->settings.decode_hdcd ? | |
| AV_CODEC_ID_PCM_S32LE : | |
| AV_CODEC_ID_PCM_S16LE); | |
| else | |
| out_codec = avcodec_find_encoder(cfmt->codec); | |
| if (!out_codec) { | |
| cyanrip_log(ctx, 0, "Codec not found (not compiled in lavc?)!\n"); | |
| ret = AVERROR(ENOMEM); | |
| goto fail; | |
| } | |
| s->avf->oformat->audio_codec = out_codec->id; | |
| /* Output avctx */ | |
| s->out_avctx = setup_out_avctx(ctx, s->avf, out_codec, cfmt); | |
| if (!s->out_avctx) { | |
| cyanrip_log(ctx, 0, "Unable to init output avctx!\n"); | |
| goto fail; | |
| } | |
| /* Set primary audio stream's parameters */ | |
| st_aud->time_base = (AVRational){ 1, s->out_avctx->sample_rate }; | |
| s->audio_stream_index = st_aud->index; | |
| /* Add metadata */ | |
| av_dict_copy(&s->avf->metadata, t->meta, 0); | |
| /* Open encoder */ | |
| ret = avcodec_open2(s->out_avctx, out_codec, NULL); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Could not open output codec context!\n"); | |
| goto fail; | |
| } | |
| /* Set codecpar */ | |
| ret = avcodec_parameters_from_context(st_aud->codecpar, s->out_avctx); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Couldn't copy codec params!\n"); | |
| goto fail; | |
| } | |
| /* Open for writing */ | |
| ret = avio_open(&s->avf->pb, filename, AVIO_FLAG_WRITE); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Couldn't open %s: %s! Invalid folder name? Try -D <folder>.\n", filename, av_err2str(ret)); | |
| goto fail; | |
| } | |
| av_freep(&filename); | |
| /* Write header */ | |
| ret = avformat_write_header(s->avf, NULL); | |
| if (ret < 0) { | |
| cyanrip_log(ctx, 0, "Couldn't write header: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| /* Mux cover image */ | |
| if (art->pkt && cfmt->coverart_supported) { | |
| AVPacket *pkt = av_packet_clone(art->pkt); | |
| pkt->stream_index = st_img->index; | |
| if ((ret = av_interleaved_write_frame(s->avf, pkt)) < 0) { | |
| cyanrip_log(ctx, 0, "Error writing picture packet: %s!\n", av_err2str(ret)); | |
| goto fail; | |
| } | |
| av_packet_free(&pkt); | |
| } | |
| /* SWR */ | |
| s->swr = setup_init_swr(ctx, s->out_avctx, ctx->settings.decode_hdcd); | |
| if (!s->swr) | |
| goto fail; | |
| /* FIFO */ | |
| s->fifo = cr_frame_fifo_create(-1, FRAME_FIFO_BLOCK_NO_INPUT); | |
| if (!s->fifo) | |
| goto fail; | |
| pthread_create(&s->thread, NULL, cyanrip_track_encoding, s); | |
| *enc_ctx = s; | |
| return 0; | |
| fail: | |
| av_free(filename); | |
| cyanrip_end_track_encoding(&s); | |
| return ret; | |
| } |