Today learning ffmpeg/doc/examples/muxing. C
Program encoding 10 seconds of audio and video data, write parameter specified path, file suffix will determine the file packaging format, use audio and video encoder, call the command as follows:
➜ examples git:(master) qualify./muxing_g/TMP /mux.mp4 ➜ examples git:(master) qualify./muxing_g/TMP /mux.movCopy the code
Create AVFormatContext
// Create AVFormatContext and guess the output format based on the file suffix
avformat_alloc_output_context2(&oc, NULL.NULL, filename);
if(! oc) {printf("Could not deduce output format from file extension: using MPEG.\n");
// Output format cannot be inferred, use mPEG
avformat_alloc_output_context2(&oc, NULL."mpeg", filename);
}
Copy the code
Add video/audio stream to AVFormatContext
if(fmt->video_codec ! = AV_CODEC_ID_NONE) { add_stream(&video_st, oc, &video_codec, fmt->video_codec); have_video =1;
encode_video = 1;
}
if(fmt->audio_codec ! = AV_CODEC_ID_NONE) { add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec); have_audio =1;
encode_audio = 1;
}
Copy the code
What does add_stream do
/* Add an output stream. */
static void add_stream(OutputStream *ost, AVFormatContext *oc,
const AVCodec **codec,
enum AVCodecID codec_id)
{
AVCodecContext *c;
int i;
/* find the encoder */
// find AVCodec for codec_id
*codec = avcodec_find_encoder(codec_id);
/ / create the PKT
ost->tmp_pkt = av_packet_alloc();
/ / create the AVStream
ost->st = avformat_new_stream(oc, NULL);
// Set the stream index
ost->st->id = oc->nb_streams- 1;
// Create the encoder context
c = avcodec_alloc_context3(*codec);
// Save the encoder context
ost->enc = c;
switch ((*codec)->type) {
case AVMEDIA_TYPE_AUDIO:
// Set the audio encoder context parameters (bit rate, sample rate, bit depth, channel layout, etc.).// Set the stream time base
ost->st->time_base = (AVRational){ 1, c->sample_rate };
break;
case AVMEDIA_TYPE_VIDEO:
// Set the encoder context parameters
c->codec_id = codec_id;
// Set the stream time base
ost->st->time_base = (AVRational){ 1, STREAM_FRAME_RATE };
c->time_base = ost->st->time_base;
// Encoder bit rate, frame rate, gop, resolution, Settings.break;
default:
break;
}
/* Some formats want stream headers to be separate. */
// Handle the stream header
if (oc->oformat->flags & AVFMT_GLOBALHEADER)
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
Copy the code
-
Find AVCodec based on codec_id
-
Call avformat_new_stream to create the stream
-
Set the stream index
-
Configure the encoder parameters according to the context in which the encoder is created by AVCodec
-
Set time_base for stream
-
Handle the flag bit for stream Headers
With the audio and video stream created, the next step is to prepare for writing
-
The stream is created above, and the encoder context is created. Open_video /open_audio continues to prepare for the write
-
When ready, open the file for writing
/* Now that all the parameters are set, we can open the audio and * video codecs and allocate the necessary encode buffers. */
if (have_video)
open_video(oc, video_codec, &video_st, opt);
if (have_audio)
open_audio(oc, audio_codec, &audio_st, opt);
// Print the oc information
av_dump_format(oc, 0, filename, 1);
/* open the output file, if needed */
if(! (fmt->flags & AVFMT_NOFILE)) {// Open the file
ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr."Could not open '%s': %s\n", filename,
av_err2str(ret));
return 1; }}Copy the code
See what Open_video does
static void open_video(AVFormatContext *oc, const AVCodec *codec,
OutputStream *ost, AVDictionary *opt_arg)
{
int ret;
AVCodecContext *c = ost->enc;
AVDictionary *opt = NULL;
// copy the contents of opt_arg to opt
av_dict_copy(&opt, opt_arg, 0);
/* open the codec */
// Open the encoder
ret = avcodec_open2(c, codec, &opt);
/ / release opt
av_dict_free(&opt);
if (ret < 0) {
fprintf(stderr."Could not open video codec: %s\n", av_err2str(ret));
exit(1);
}
/* allocate and init a re-usable frame */
// Create a reusable AVFrame according to the format, width and height
ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
if(! ost->frame) {fprintf(stderr."Could not allocate video frame\n");
exit(1);
}
/* If the output format is not YUV420P, then a temporary YUV420P * picture is needed too. It is then converted to the required * output format. */
ost->tmp_frame = NULL;
if(c->pix_fmt ! = AV_PIX_FMT_YUV420P) {// If pix_fmt is not yuv420p, create a yuv420p AVFrame and save it in oST ->tmp_frame
ost->tmp_frame = alloc_picture(AV_PIX_FMT_YUV420P, c->width, c->height);
if(! ost->tmp_frame) {fprintf(stderr."Could not allocate temporary picture\n");
exit(1); }}/* copy the stream parameters to the muxer */
// Copy the encoder's parameters to the stream's corresponding encoding parameters
ret = avcodec_parameters_from_context(ost->st->codecpar, c);
if (ret < 0) {
fprintf(stderr."Could not copy the stream parameters\n");
exit(1); }}Copy the code
-
Open encoder
-
Apply for resources and create an AVFrame to store pre-coding data
-
Call avCODEC_parameterS_FROm_context to copy the encoder’s encoding parameters into the STREAM’s COdecPAR
Write audio and video data to files
Write the file header
/* Write the stream header, if any. */
// Write the stream information to the file header
ret = avformat_write_header(oc, &opt);
if (ret < 0) {
fprintf(stderr."Error occurred when opening output file: %s\n",
av_err2str(ret));
return 1;
}
Copy the code
Alternate writing audio and video frames
while (encode_video || encode_audio) {
/* select the stream to encode */
/* Alternately write encoded audio and video frames at the end of the video write or video_st.next_pts <= Audio_st.next_pts, write video or write audio */
if(encode_video && (! encode_audio || av_compare_ts(video_st.next_pts, video_st.enc->time_base, audio_st.next_pts, audio_st.enc->time_base) <=0)) {
// Write the encoded video frameencode_video = ! write_video_frame(oc, &video_st); }else {
// Write the encoded audio frame
encode_audio = !write_audio_frame(oc, &audio_st);
}
}
Copy the code
Write a trailer
/* Write the trailer, if any. The trailer must be written before you * close the CodecContexts open when you wrote the header; otherwise * av_write_trailer() may try to use memory that was freed on * av_codec_close(). */
av_write_trailer(oc);
Copy the code
Close encoder, close file, release resources
/* Close each codec. */
if (have_video)
close_stream(oc, &video_st);
if (have_audio)
close_stream(oc, &audio_st);
if(! (fmt->flags & AVFMT_NOFILE))/* Close the output file. */
avio_closep(&oc->pb);
/* free the stream */
avformat_free_context(oc);
Copy the code
write_video_frame
static int write_video_frame(AVFormatContext *oc, OutputStream *ost)
{
return write_frame(oc, ost->enc, ost->st, get_video_frame(ost), ost->tmp_pkt);
}
Copy the code
We simply call write_frame
static int write_frame(AVFormatContext *fmt_ctx, AVCodecContext *c, AVStream *st, AVFrame *frame, AVPacket *pkt)
{
int ret;
// send the frame to the encoder
// Send the frame to the encoder to encode
ret = avcodec_send_frame(c, frame);
if (ret < 0) {
fprintf(stderr."Error sending a frame to the encoder: %s\n",
av_err2str(ret));
exit(1);
}
while (ret >= 0) {
// Read encoded PKT from encoder
ret = avcodec_receive_packet(c, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
break;
else if (ret < 0) {
fprintf(stderr."Error encoding a frame: %s\n", av_err2str(ret));
exit(1);
}
/* rescale output packet timestamp values from codec to stream timebase */
// Adjust PKT's PTS, PKT's time base reference encoder's time base, convert it to reference stream's time base
av_packet_rescale_ts(pkt, c->time_base, st->time_base);
/ / set PKT stream_index and stream corresponding consistent, audio and video respectively corresponding to different stream_index
pkt->stream_index = st->index;
/* Write the compressed frame to the media file. */
log_packet(fmt_ctx, pkt);
// Write PKT to the video file
ret = av_interleaved_write_frame(fmt_ctx, pkt);
/* pkt is now blank (av_interleaved_write_frame() takes ownership of * its contents and resets pkt), so that no unreferencing is necessary. * This would be different if one used av_write_frame(). */
if (ret < 0) {
fprintf(stderr."Error while writing output packet: %s\n", av_err2str(ret));
exit(1); }}return ret == AVERROR_EOF ? 1 : 0;
}
Copy the code
-
Send AVFrame to encoder to encode
-
Read the PKT
-
Call av_packet_rescale_ts to adjust the PKT timestamp, and write PTS to the stream against time_base
-
Set PKT index
-
Call av_interleaved_write_frame to write PKT to a file
-
Returns the write result. When AVFrame is empty, the encoder is flushed and ret = AVERROR_EOF returns 1, ending the write
Each frame of frame data is filled with fake data from the get_video_frame method
static AVFrame *get_video_frame(OutputStream *ost)
{
AVCodecContext *c = ost->enc;
/* check if we want to generate more frames */
if (av_compare_ts(ost->next_pts, c->time_base,
STREAM_DURATION, (AVRational){ 1.1 }) > 0)
return NULL;
/* when we pass a frame to the encoder, it may keep a reference to it * internally; make sure we do not overwrite it here */
if (av_frame_make_writable(ost->frame) < 0)
exit(1);
if(c->pix_fmt ! = AV_PIX_FMT_YUV420P) {/* as we only generate a YUV420P picture, we must convert it * to the codec pixel format if needed */
if(! ost->sws_ctx) { ost->sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_YUV420P, c->width, c->height, c->pix_fmt, SCALE_FLAGS,NULL.NULL.NULL);
if(! ost->sws_ctx) {fprintf(stderr."Could not initialize the conversion context\n");
exit(1);
}
}
fill_yuv_image(ost->tmp_frame, ost->next_pts, c->width, c->height);
sws_scale(ost->sws_ctx, (const uint8_t * const *) ost->tmp_frame->data,
ost->tmp_frame->linesize, 0, c->height, ost->frame->data,
ost->frame->linesize);
} else {
fill_yuv_image(ost->frame, ost->next_pts, c->width, c->height);
}
ost->frame->pts = ost->next_pts++;
return ost->frame;
}
Copy the code
-
Call av_compare_ts to determine whether to continue generating a new frame. The initial rule is to write data for 10 seconds, and then stop writing after that
-
Av_frame_make_writable makes the current frame writable, but the frame referenced by the encoder is not writable. If this method is referenced, a new buF is created internally, making it writable
-
Fill in the data YUV, but also do the zoom processing, not to discuss
-
Update frame’s PTS
-
Returns the generated frame
write_audio_frame
/* * encode one audio frame and send it to the muxer * return 1 when encoding is finished, 0 otherwise */
static int write_audio_frame(AVFormatContext *oc, OutputStream *ost)
{
AVCodecContext *c;
AVFrame *frame;
int ret;
int dst_nb_samples;
c = ost->enc;
// Get the audio frame
frame = get_audio_frame(ost);
if (frame) {
/* convert samples from native format to destination codec format, using the resampler */
/* compute destination number of samples */
/* Calculate the number of samples that should be generated based on resampling */
dst_nb_samples = av_rescale_rnd(swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples,
c->sample_rate, c->sample_rate, AV_ROUND_UP);
// Because the sampling rate is not changed, only the bit depth is changed, the number of samples remains unchanged
av_assert0(dst_nb_samples == frame->nb_samples);
/* when we pass a frame to the encoder, it may keep a reference to it * internally; * make sure we do not overwrite it here */
// make ost->frame writable
ret = av_frame_make_writable(ost->frame);
if (ret < 0)
exit(1);
/* convert to destination format */
/ / re-sampling
ret = swr_convert(ost->swr_ctx,
ost->frame->data, dst_nb_samples,
(const uint8_t **)frame->data, frame->nb_samples);
if (ret < 0) {
fprintf(stderr."Error while converting\n");
exit(1);
}
frame = ost->frame;
// Recalculate the audio PTS
frame->pts = av_rescale_q(ost->samples_count, (AVRational){1, c->sample_rate}, c->time_base);
/ / samples_count calculation
ost->samples_count += dst_nb_samples;
}
// Write the audio frame to the file
return write_frame(oc, c, ost->st, frame, ost->tmp_pkt);
}
Copy the code
-
To prepare an AVFrame, call get_audio_frame to get an AVFrame and fill it with PCM fake data according to the encoding format
-
Call AV_rescale_rnd to calculate the number of resampling. If the sampling rate of the data source and the audio feed into the encoder is different, the sampling rate needs to be changed. The sample program only changes the sampling format, but does not change the sampling rate, and the number of samples remains unchanged
-
Resampling is done and the resampling data is saved in oST -> Frame
-
After resampling, the PTS of the frame needs to be updated
-
Call the write_frame encoding to write data to a file
Conclusion:
Learn how to encode and encapsulate audio and video data into files by using libavformat
-
Create AVFormatContext
-
Add the stream
-
Configure the codec, stream parameters
-
Encode frame, generate PKT, update PTS
-
Alternate write audio and video PKT
-
Close the codec and end writing the file