I'm trying to copy all streams from several files into one file without transcoding streams. Something you usually do with ffmpeg
utility by ffmpeg -i “file_with_audio.mp4” -i “file_with_video.mp4” -c copy -shortest file_with_audio_and_video.mp4
This is the code:
int ffmpegOpenInputFile(const char* filename, AVFormatContext **ic) {
int ret;
unsigned int i;
*ic = avformat_alloc_context();
if (!(*ic))
return -1; // Couldn't allocate input context
if((ret = avformat_open_input(ic, filename, NULL, NULL)) < 0)
return ret; // Couldn't open file
// Get format info (retrieve stream information)
if ((ret = avformat_find_stream_info(*ic, NULL)) < 0)
return ret; // Couldn't find stream information
for (int i = 0; i < (*ic)->nb_streams; i++) {
AVStream *stream;
AVCodecContext *codec_ctx;
stream = (*ic)->streams[i];
codec_ctx = stream->codec;
/* Reencode video & audio and remux subtitles etc. */
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO
|| codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) {
/* Open decoder */
ret = avcodec_open2(codec_ctx,
avcodec_find_decoder(codec_ctx->codec_id), NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);
return ret;
}
}
}
// Dump information about file onto standard error
av_dump_format(*ic, 0, filename, 0);
return 0;
}
int main(int argc, char *argv[]) {
const char *inputFilename1 = "/avfiles/video_input.mp4";
const char *inputFilename2 = "/avfiles/audio_input.mp4";
const char *filename = "/avfiles/out.mp4";
int ret;
av_register_all();
AVFormatContext *ic1 = nullptr;
AVFormatContext *ic2 = nullptr;
AVFormatContext *oc = nullptr;
if ((ret = ffmpegOpenInputFile(inputFilename1, &ic1)) < 0)
return ret; // and free resources and
if ((ret = ffmpegOpenInputFile(inputFilename2, &ic2)) < 0)
return ret; // and free resources and
AVOutputFormat *outfmt = av_guess_format(NULL, filename, NULL);
if (outfmt == NULL)
return -1; // Could not guess output format
avformat_alloc_output_context2(&oc, outfmt, NULL, filename);
if (!oc)
return AVERROR_UNKNOWN; // Could not create output context
// populate input streams from all input files
AVStream **input_streams = NULL;
int nb_input_streams = 0;
for (int i = 0; i < ic1->nb_streams; i++) {
input_streams = (AVStream **) grow_array(input_streams, sizeof(*input_streams), &nb_input_streams,
nb_input_streams + 1);
input_streams[nb_input_streams - 1] = ic1->streams[i];
}
for (int i = 0; i < ic2->nb_streams; i++) {
input_streams = (AVStream **) grow_array(input_streams, sizeof(*input_streams), &nb_input_streams,
nb_input_streams + 1);
input_streams[nb_input_streams - 1] = ic2->streams[i];
}
for (int i = 0; i < nb_input_streams; i++) {
AVStream *ist = input_streams[i]; // could be named 'm_in_vid_strm'
// if output context has video codec support and current input stream is video
if (/*oc->video_codec_id*/ oc->oformat->video_codec != AV_CODEC_ID_NONE && ist != NULL
&& ist->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
AVCodec *out_vid_codec = avcodec_find_encoder(oc->oformat->video_codec);
if (NULL == out_vid_codec)
return -1; // Couldn't find video encoder
AVStream *m_out_vid_strm = avformat_new_stream(oc, out_vid_codec);
if (NULL == m_out_vid_strm)
return -1; // Couldn't output video stream
m_out_vid_strm->id = 0; // XXX:
ret = avcodec_copy_context(m_out_vid_strm->codec, ist->codec);
if (ret < 0)
return ret; // Failed to copy context
}
// if output context has audio codec support and current input stream is audio
if (/*oc->audio_codec_id*/ oc->oformat->audio_codec != AV_CODEC_ID_NONE && ist != NULL
&& ist->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
AVCodec *out_aud_codec = avcodec_find_encoder(oc->oformat->audio_codec);
if (nullptr == out_aud_codec)
return -1; // couldn't find audio codec
AVStream *m_out_aud_strm = avformat_new_stream(oc, out_aud_codec);
if (nullptr == m_out_aud_strm)
return -1; // couldn't allocate audio out stream
ret = avcodec_copy_context(m_out_aud_strm->codec, ist->codec);
if (ret < 0)
return ret; // couldn't copy context
}
}
// finally output header
if (!(oc->flags & AVFMT_NOFILE)) {
ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0)
return ret; // Could not open output file
av_dump_format(oc, 0, filename, 1);
ret = avformat_write_header(oc, NULL);
if (ret < 0)
return ret; // Error occurred when opening output file
}
return 0;
}
avformat_write_header(oc, NULL);
always return error and I see this messages:
[mp4 @ 0x7f84ec900a00] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
[mp4 @ 0x7f84ec900a00] Tag avc1/0x31637661 incompatible with output codec id '28' ([33][0][0][0])
But input and output streams match:
Input streams from 2 files:
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 2834 kb/s, 23.98 fps, 23.98 tbr, 90k tbn, 47.95 tbc (default)
Stream #0:0(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 125 kb/s (default)
Output #0, mp4, to '/Users/alex/Workspace/_qt/tubisto/avfiles/out.mp4':
Stream #0:0: Video: h264 (libx264) (avc1 / 0x31637661), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], q=2-31, 2834 kb/s, 47.95 tbc
Stream #0:1: Audio: aac (libvo_aacenc) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 125 kb/s
Why the error with incompatible output codec happens? What is wrong in my code and how to make it work to copy all streams from all input files to output file?
The reason is because you didn't set the encoder's codec_tag
correctly.
You should set the correct codec_tag
, or set the codec_tag
to 0 and leave the muxer to handle for you.
Following is a sample code how to do.
AVFormatContext *ofmt_ctx; // todo
AVCodec *oc; // todo
AVStream *stream = avformat_new_stream(ofmt_ctx, oc);
if (stream != NULL) {
// ...
unsigned int tag = 0;
// for ffmpeg new api 3.x
AVCodecParameters *parameters = stream->codecpar;
if (av_codec_get_tag2(ofmt_ctx->oformat->codec_tag, oc->id, &tag) == 0) {
av_log(NULL, AV_LOG_ERROR, "could not find codec tag for codec id %d, default to 0.\n", oc->id);
}
parameters->codec_tag = tag;
stream->codec = avcodec_alloc_context3(oc);
// more setting for stream->codec
avcodec_parameters_to_context(stream->codec, parameters);
// for old ffmpeg version
stream->codec->codec_tag = tag;
// ...
}