ffmpeg官方的转码例子transcoding.c不涉及mp4的转码。
本例推流包括两种方式推流:用mp4文件推流,或者通过dshow采集摄像头和麦克风的音视频流再推流。
详细注释已添加到代码中,后面会持续完善。
待跟踪项:
1.硬件编码(h264_nvenc)的支持
2.声音/视频pts、dts未完全同步。
transcodingRtmpThread1.cpp
#include "transcodingRtmpThread1.h"
RTMPClient::~RTMPClient()
{
int i;
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
avcodec_free_context(&scs[i].dec_ctx);
if (ofmt_ctx && ofmt_ctx->nb_streams > i && ofmt_ctx->streams[i] && scs[i].enc_ctx)
avcodec_free_context(&scs[i].enc_ctx);
if (filter_ctx && filter_ctx[i].filter_graph)
{
avfilter_graph_free(&filter_ctx[i].filter_graph);
av_packet_free(&filter_ctx[i].enc_pkt);
av_frame_free(&filter_ctx[i].filtered_frame);
}
av_frame_free(&scs[i].dec_frame);
}
av_free(filter_ctx);
av_free(scs);
avformat_close_input(&ifmt_ctx);
if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
avformat_free_context(ofmt_ctx);
}
int RTMPClient::open_input_file()
{
int ret;
unsigned int i;
AVDictionary *dictionary = NULL;
AVInputFormat *ifmt = NULL;
ifmt_ctx = avformat_alloc_context();
avformat_network_init();
av_dict_set_int(&dictionary, "rtbufsize", 3041280 * 100, 0);
#if USE_CAM
avdevice_register_all();
Logitech HD Webcam C525 Integrated Webcam
const char *video = "video=Logitech HD Webcam C525:audio=麦克风 (HD Webcam C525)";
ifmt = av_find_input_format("dshow");
// av_dict_set(&dictionary, "video_size", "1280x720", 0);
av_dict_set(&dictionary, "video_size", "1600*896", 0);
// av_dict_set(&dictionary, "input_format", "mjpeg", 0);
av_dict_set(&dictionary, "framerate", "30", 0);
// ifmt_ctx->video_codec_id = AV_CODEC_ID_MJPEG;
// av_format_set_video_codec(ifmt_ctx, opened_mjpeg_codec);
#else
const char *video = "d:\\ss_1min.mp4";
#endif
if ((ret = avformat_open_input(&ifmt_ctx, video, ifmt, &dictionary)) != 0)
{
printf("Couldn't open input videostream.\n");
return -1;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
return ret;
}
scs = (StreamContext *)av_mallocz_array(ifmt_ctx->nb_streams, sizeof(*scs));
if (!scs)
return AVERROR(ENOMEM);
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
AVStream *stream = ifmt_ctx->streams[i];
AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);
AVCodecContext *codec_ctx;
if (!dec)
{
av_log(NULL, AV_LOG_ERROR, "Failed to find decoder for stream #%u\n", i);
return AVERROR_DECODER_NOT_FOUND;
}
codec_ctx = avcodec_alloc_context3(dec);
if (!codec_ctx)
{
av_log(NULL, AV_LOG_ERROR, "Failed to allocate the decoder context for stream #%u\n", i);
return AVERROR(ENOMEM);
}
ret = avcodec_parameters_to_context(codec_ctx, stream->codecpar);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed copy decoder parameters to dec_context for stream #%u\n", i);
return ret;
}
/* Reencode video & audio and remux subtitles etc. */
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || codec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
if (codec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
codec_ctx->framerate = av_guess_frame_rate(ifmt_ctx, stream, NULL);
/* Open decoder */
ret = avcodec_open2(codec_ctx, dec, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to open decoder for stream #%u\n", i);
return ret;
}
}
scs[i].dec_ctx = codec_ctx;
scs[i].dec_frame = av_frame_alloc();
if (!scs[i].dec_frame)
return AVERROR(ENOMEM);
}
av_dump_format(ifmt_ctx, 0, video, 0);
return 0;
}
int RTMPClient::open_output_file()
{
int ret;
unsigned int i;
ofmt_ctx = NULL;
#if USE_RTMP
const char *outUrl = "rtmp://127.0.0.1:9014/live/logic";
#else
const char *outUrl = "D:\\ss_10min_out.flv";
#endif
avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", outUrl);
if (!ofmt_ctx)
{
av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
return AVERROR_UNKNOWN;
}
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
AVStream *out_stream;
AVStream *in_stream;
AVCodecContext *dec_ctx, *enc_ctx;
AVCodec *encoder;
in_stream = ifmt_ctx->streams[i];
dec_ctx = scs[i].dec_ctx;
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
/* in this example, we choose transcoding to same codec */
encoder = avcodec_find_encoder(dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO ? AV_CODEC_ID_H264 : AV_CODEC_ID_AAC);
if (!encoder)
{
av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n");
return AVERROR_INVALIDDATA;
}
out_stream = avformat_new_stream(ofmt_ctx, encoder);
if (!out_stream)
{
av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n");
return AVERROR_UNKNOWN;
}
enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx)
{
av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
return AVERROR(ENOMEM);
}
/* In this example, we transcode to same properties (picture size,
* sample rate etc.). These properties can be changed for output
* streams easily using filters */
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
vedio_index = i;
enc_ctx->height = 720;
enc_ctx->width = 1280;
enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
/* take first format from list of supported formats */
if (encoder->pix_fmts)
enc_ctx->pix_fmt = encoder->pix_fmts[0];
else
enc_ctx->pix_fmt = dec_ctx->pix_fmt;
/* video time_base can be set to whatever is handy and supported by encoder */
enc_ctx->time_base = av_inv_q(dec_ctx->framerate); //编码器必须设置,解码器不用设置(已经弃用),因为解码器已经确定了频率
// enc_ctx->framerate = dec_ctx->framerate;
enc_ctx->bit_rate = 1000000;
enc_ctx->gop_size = 10; // 帧组最多10帧一组
enc_ctx->max_b_frames = 0; //设置无B帧
enc_ctx->codec_tag = 0; //
}
else
{
audio_index = i;
if (!encoder->channel_layouts)
enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO;
else
{
int best_nb_channels = 0;
const uint64_t *p = encoder->channel_layouts;
while (*p)
{
int nb_channels = av_get_channel_layout_nb_channels(*p);
if (nb_channels > best_nb_channels)
{
enc_ctx->channel_layout = *p;
best_nb_channels = nb_channels;
}
p++;
}
}
enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
enc_ctx->sample_rate = 0;
if (!encoder->supported_samplerates)
enc_ctx->sample_rate = 44100;
else
{
const int *p = encoder->supported_samplerates;
while (*p)
{
if (!enc_ctx->sample_rate || abs(44100 - *p) < abs(44100 - enc_ctx->sample_rate))
enc_ctx->sample_rate = *p;
p++;
}
}
enc_ctx->bit_rate = 64000;
enc_ctx->sample_fmt = encoder->sample_fmts[0];
const enum AVSampleFormat *p = encoder->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE)
{
if (*p == enc_ctx->sample_fmt)
{
ret = 1;
break;
}
p++;
}
if (ret <= 0)
{
fprintf(stderr, "Encoder does not support sample format %s",
av_get_sample_fmt_name(enc_ctx->sample_fmt));
exit(1);
}
// // enc_ctx->channel_layout = av_get_default_channel_layout(AV_CH_LAYOUT_STEREO); // dec_ctx->channel_layout; //
// enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO; // dec_ctx->channel_layout; //
// enc_ctx->channels = 2;
// enc_ctx->sample_rate = dec_ctx->sample_rate;
// enc_ctx->sample_fmt = encoder->sample_fmts[0];
// // enc_ctx->time_base = (AVRational){1, enc_ctx->sample_rate}; //???
// enc_ctx->bit_rate = 96000;
// enc_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
}
if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
AVDictionary *param = 0;
av_dict_set(¶m, "preset", "medium", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
/* Third parameter can be used to pass settings to encoder */
ret = avcodec_open2(enc_ctx, encoder, ¶m);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i);
return ret;
}
ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream #%u\n", i);
return ret;
}
//这里设置应该没用的,因为封装格式(MP4\FLV)就决定了输出的timebase,一般都是{1,1000}
// out_stream->time_base = enc_ctx->time_base;
scs[i].enc_ctx = enc_ctx;
scs[i].out_stream = out_stream;
}
else if (dec_ctx->codec_type == AVMEDIA_TYPE_UNKNOWN)
{
av_log(NULL, AV_LOG_FATAL, "Elementary stream #%d is of unknown type, cannot proceed\n", i);
return AVERROR_INVALIDDATA;
}
else
{
/* if this stream must be remuxed */
ret = avcodec_parameters_copy(out_stream->codecpar, in_stream->codecpar);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Copying parameters for stream #%u failed\n", i);
return ret;
}
out_stream->time_base = in_stream->time_base;
}
}
av_dump_format(ofmt_ctx, 0, outUrl, 1);
if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE))
{
ret = avio_open(&ofmt_ctx->pb, outUrl, AVIO_FLAG_WRITE);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Could not open output file '%s'", outUrl);
return ret;
}
}
/* init muxer, write output file header */
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n");
return ret;
}
// 输入流:打开输入文件后调用avformat_find_stream_info()可获取到每个流中的time_base
// 输出流:打开输出文件后调用avformat_write_header()可根据输出文件封装格式确定每个流的time_base并写入输出文件中
//打印输出流的timebase
if (vedio_index != -1)
{
StreamContext *sc1 = &scs[vedio_index];
AVRational instream_timebase = ifmt_ctx->streams[vedio_index]->time_base;
AVRational outstream_timebase = ofmt_ctx->streams[vedio_index]->time_base;
AVRational dec_ctx_timebase = sc1->dec_ctx->time_base;
// stream->dec_ctx->time_base = av_inv_q(stream->dec_ctx->framerate); //修正解码流中的时间戳
av_log(NULL, AV_LOG_ERROR, "vedio_index timebases\n");
}
if (audio_index != -1)
{
StreamContext *sc2 = &scs[audio_index];
AVRational instream_timebase = ifmt_ctx->streams[audio_index]->time_base;
AVRational outstream_timebase = ofmt_ctx->streams[audio_index]->time_base;
AVRational dec_ctx_timebase = sc2->dec_ctx->time_base;
av_log(NULL, AV_LOG_ERROR, "audio_index timebases\n");
}
return 0;
}
int RTMPClient::init_filter(FilteringContext *fctx, AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx, const char *filter_spec)
{
char args[512];
int ret = 0;
const AVFilter *buffersrc = NULL;
const AVFilter *buffersink = NULL;
AVFilterContext *buffersrc_ctx = NULL;
AVFilterContext *buffersink_ctx = NULL;
AVFilterInOut *outputs = avfilter_inout_alloc();
AVFilterInOut *inputs = avfilter_inout_alloc();
AVFilterGraph *filter_graph = avfilter_graph_alloc();
if (!outputs || !inputs || !filter_graph)
{
ret = AVERROR(ENOMEM);
goto end;
}
if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO)
{
buffersrc = avfilter_get_by_name("buffer");
buffersink = avfilter_get_by_name("buffersink");
if (!buffersrc || !buffersink)
{
av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n");
ret = AVERROR_UNKNOWN;
goto end;
}
snprintf(args, sizeof(args),
"video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
dec_ctx->time_base.num, dec_ctx->time_base.den,
dec_ctx->sample_aspect_ratio.num,
dec_ctx->sample_aspect_ratio.den);
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer source\n");
goto end;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create buffer sink\n");
goto end;
}
ret = av_opt_set_bin(buffersink_ctx, "pix_fmts",
(uint8_t *)&enc_ctx->pix_fmt, sizeof(enc_ctx->pix_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot set output pixel format\n");
goto end;
}
}
else if (dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO)
{
buffersrc = avfilter_get_by_name("abuffer");
buffersink = avfilter_get_by_name("abuffersink");
if (!buffersrc || !buffersink)
{
av_log(NULL, AV_LOG_ERROR, "filtering source or sink element not found\n");
ret = AVERROR_UNKNOWN;
goto end;
}
if (!dec_ctx->channel_layout)
dec_ctx->channel_layout =
av_get_default_channel_layout(dec_ctx->channels);
snprintf(args, sizeof(args),
"time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=0x%" PRIx64,
dec_ctx->time_base.num, dec_ctx->time_base.den, dec_ctx->sample_rate,
av_get_sample_fmt_name(dec_ctx->sample_fmt),
dec_ctx->channel_layout);
ret = avfilter_graph_create_filter(&buffersrc_ctx, buffersrc, "in",
args, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer source\n");
goto end;
}
ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink, "out",
NULL, NULL, filter_graph);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot create audio buffer sink\n");
goto end;
}
ret = av_opt_set_bin(buffersink_ctx, "sample_fmts",
(uint8_t *)&enc_ctx->sample_fmt, sizeof(enc_ctx->sample_fmt),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample format\n");
goto end;
}
ret = av_opt_set_bin(buffersink_ctx, "channel_layouts",
(uint8_t *)&enc_ctx->channel_layout,
sizeof(enc_ctx->channel_layout), AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot set output channel layout\n");
goto end;
}
ret = av_opt_set_bin(buffersink_ctx, "sample_rates",
(uint8_t *)&enc_ctx->sample_rate, sizeof(enc_ctx->sample_rate),
AV_OPT_SEARCH_CHILDREN);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Cannot set output sample rate\n");
goto end;
}
}
else
{
ret = AVERROR_UNKNOWN;
goto end;
}
/* Endpoints for the filter graph. */
outputs->name = av_strdup("in");
outputs->filter_ctx = buffersrc_ctx;
outputs->pad_idx = 0;
outputs->next = NULL;
inputs->name = av_strdup("out");
inputs->filter_ctx = buffersink_ctx;
inputs->pad_idx = 0;
inputs->next = NULL;
if (!outputs->name || !inputs->name)
{
ret = AVERROR(ENOMEM);
goto end;
}
if ((ret = avfilter_graph_parse_ptr(filter_graph, filter_spec,
&inputs, &outputs, NULL)) < 0)
goto end;
if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0)
goto end;
/* Fill FilteringContext */
fctx->buffersrc_ctx = buffersrc_ctx;
fctx->buffersink_ctx = buffersink_ctx;
fctx->filter_graph = filter_graph;
end:
avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);
return ret;
}
int RTMPClient::init_filters(void)
{
const char *filter_spec;
unsigned int i;
int ret;
filter_ctx = (FilteringContext *)av_malloc_array(ifmt_ctx->nb_streams, sizeof(*filter_ctx));
if (!filter_ctx)
return AVERROR(ENOMEM);
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
filter_ctx[i].buffersrc_ctx = NULL;
filter_ctx[i].buffersink_ctx = NULL;
filter_ctx[i].filter_graph = NULL;
if (!(ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO || ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO))
continue;
// PZX 滤镜暂时为空
if (ifmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
// filter_spec = "null"; /* passthrough (dummy) filter for video */
filter_spec = "format=yuv420p,scale=1280:-1,rotate=PI/12"; /* passthrough (dummy) filter for video */
else
filter_spec = "anull"; /* passthrough (dummy) filter for audio */
ret = init_filter(&filter_ctx[i], scs[i].dec_ctx,
scs[i].enc_ctx, filter_spec);
if (ret)
return ret;
filter_ctx[i].enc_pkt = av_packet_alloc();
if (!filter_ctx[i].enc_pkt)
return AVERROR(ENOMEM);
filter_ctx[i].filtered_frame = av_frame_alloc();
if (!filter_ctx[i].filtered_frame)
return AVERROR(ENOMEM);
}
return 0;
}
int RTMPClient::encode_write_frame(unsigned int stream_index, int flush)
{
StreamContext *sc = &scs[stream_index];
FilteringContext *filter = &filter_ctx[stream_index];
AVFrame *filt_frame = flush ? NULL : filter->filtered_frame;
AVPacket *enc_pkt = filter->enc_pkt;
int ret;
av_packet_unref(enc_pkt);
AVCodecContext *enc_ctx = sc->enc_ctx;
AVCodecContext *dec_ctx = sc->dec_ctx;
//音频是否需要重采样?
bool needResample = stream_index == audio_index && (enc_ctx->sample_fmt != dec_ctx->sample_fmt || enc_ctx->channels != dec_ctx->channels || enc_ctx->sample_rate != dec_ctx->sample_rate);
if (needResample)
{ //音频的FIFO处理
// 音频输入源和输出源帧大小可能不一致,需对当前帧或重采样后的帧进行缓冲变换
if (av_audio_fifo_write(fifo, (void **)filt_frame->data, filt_frame->nb_samples) < 0)
return 0; //结束本轮转码
while (av_audio_fifo_size(fifo) >= enc_ctx->frame_size)
{
AVFrame *frame = av_frame_alloc();
frame->nb_samples = enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024;
frame->channel_layout = enc_ctx->channel_layout;
frame->format = enc_ctx->sample_fmt;
frame->channels = av_get_default_channel_layout(enc_ctx->channel_layout);
frame->sample_rate = enc_ctx->sample_rate;
if (frame->nb_samples)
{
int error = 0;
if ((error = av_frame_get_buffer(frame, 0)) < 0)
{
// av_log(NULL, AV_LOG_ERROR, "[pzx] frame->nb_samples size %d\n", frame->nb_samples);
av_frame_free(&frame);
return error;
}
int ret = av_audio_fifo_read(fifo, (void **)frame->data, (enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024));
#if OPENLOG
av_log(NULL, AV_LOG_INFO, "[pzx] ret:%d ,fifo size:%d,frame->nb_samples size:%d\n", ret, av_audio_fifo_size(fifo), frame->nb_samples);
#endif
if (ret < 0)
{
fprintf(stderr, "Error read audio buffer\n");
av_frame_free(&frame);
return -1;
}
ret = av_frame_make_writable(frame);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "av_frame_make_writable failed");
av_frame_free(&frame);
return -1;
}
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] 1.avcodec_send_frame ret<0 %d\n", ret);
av_frame_free(&frame);
return -1;
}
while (ret >= 0)
{
AVPacket *new_enc_pkt = av_packet_alloc();
if (!new_enc_pkt)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] 1.av_packet_alloc failed\n");
av_frame_free(&frame);
return -1;
}
ret = avcodec_receive_packet(enc_ctx, new_enc_pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
//这两种错误应该结束循环
av_packet_unref(new_enc_pkt);
break;
}
else if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] 1.avcodec_receive_packet ret<0 %d\n", ret);
av_frame_free(&frame);
av_packet_unref(new_enc_pkt);
return ret;
}
int audioIndex = sc->next_pts;
new_enc_pkt->pts = audioIndex * enc_ctx->frame_size;
new_enc_pkt->dts = audioIndex * enc_ctx->frame_size;
new_enc_pkt->duration = enc_ctx->frame_size;
new_enc_pkt->stream_index = stream_index;
sc->next_pts++;
// audioIndex++;
log_packet(ofmt_ctx, new_enc_pkt);
// printts(new_enc_pkt);
#if USE_QUEUE
// output_packet_queue_.Push(new_enc_pkt); //换成音频队列
#else
ret = av_interleaved_write_frame(ofmt_ctx, new_enc_pkt);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] audio write failed???? %d\n", ret);
//忽略掉个别帧的异常问题,继续发送
}
av_packet_free(&new_enc_pkt);
#endif
}
av_frame_free(&frame);
}
}
return ret; //处理完返回
}
else
{
if ((ret = avcodec_send_frame(enc_ctx, filt_frame)) < 0)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] 2.avcodec_send_frame ret<0 %d\n", ret);
return ret;
}
while (ret >= 0)
{
AVPacket *new_enc_pkt = av_packet_alloc(); //每次都要新申请packet,比较关键
if (!new_enc_pkt)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] 1.av_packet_alloc failed\n");
return -1;
}
ret = avcodec_receive_packet(enc_ctx, new_enc_pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
av_packet_free(&new_enc_pkt); // EAGAIN或者AVERROR_EOF后要释放
return 0;
}
else if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] 1.avcodec_receive_packet ret<0 %d\n", ret);
av_packet_free(&new_enc_pkt); // EAGAIN或者AVERROR_EOF后要释放
return ret;
}
// av_log(NULL, AV_LOG_ERROR, "[pzx] stream_index:%d filt_frame--- pts:%d,pkt_pts:%d,pkt_dts %d,\n", new_enc_pkt->stream_index, filt_frame->pts, filt_frame->pkt_pts, filt_frame->pkt_dts);
// av_log(NULL, AV_LOG_ERROR, "[pzx] stream_index:%d enc_pkt--- pts:%d,dts:%d\n", new_enc_pkt->stream_index, new_enc_pkt->pts, new_enc_pkt->dts);
/* prepare packet for muxing */
new_enc_pkt->stream_index = stream_index;
AVRational time_base_q = {1, AV_TIME_BASE}; //单位微秒-FFMPEG内部定义
AVRational in_stream_tb = ifmt_ctx->streams[stream_index]->time_base; //输入视频层(tbn)
// AVRational in_ctx_tb = dec_ctx->time_base; //输入文件层(tbc)-一般废弃不用
AVRational out_stream_tb = ofmt_ctx->streams[stream_index]->time_base; // FLV封装定义的timebase{1,1000}
AVRational out_ctx_tb = enc_ctx->time_base; //输出文件层-一般是fps
//这个两个时间都是按微秒算的。
// pts_time=按ffmpeg内部翻译的时间算,dts解码时间应该将输入流的dts转换成标准ffmpeg处理"刻度"。
// pts_time可以看成预期编码时间,(假想解码后立刻开始编码,所以pts_time的换算需要以输入为时间戳)
int64_t pts_time = av_rescale_q(new_enc_pkt->dts, in_stream_tb, time_base_q);
// now_time可以看成实际解码了多久
// now_time=(已经流失的"刻度");现在的流"刻度"-开始开始播放的"刻度"
int64_t now_time = av_gettime() - start_time; //到1970的时间差,单位同AV_TIME_BASE一样也是微秒
#if USE_RTMP //直播文件的时候解码太快了,需要停顿下,控制帧率
//如果过快(电脑台高级,第二帧解码瞬间完成,需要等待会再发送。如果是转码可以忽略),需要停顿后再解码
if (pts_time > now_time)
{
av_log(NULL, AV_LOG_ERROR, "av_usleep= %d\n", (unsigned int)(pts_time - now_time));
av_usleep((unsigned int)(pts_time - now_time));
}
#endif
av_packet_rescale_ts(new_enc_pkt, out_ctx_tb, out_stream_tb);
// log_packet(ofmt_ctx, new_enc_pkt);
printts(new_enc_pkt);
sc->next_pts++;
/* mux encoded frame */
#if USE_QUEUE
output_packet_queue_.Push(new_enc_pkt); //换成视频队列
#else
ret = av_interleaved_write_frame(ofmt_ctx, new_enc_pkt);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "[pzx] video write failed???? %d\n", ret);
}
av_packet_free(&new_enc_pkt);
#endif
}
return ret;
}
}
int RTMPClient::filter_encode_write_frame(AVFrame *frame, unsigned int stream_index)
{
FilteringContext *filter = &filter_ctx[stream_index];
int ret;
// av_log(NULL, AV_LOG_INFO, "Pushing decoded frame to filters\n");
/* push the decoded frame into the filtergraph */
ret = av_buffersrc_add_frame_flags(filter->buffersrc_ctx,
frame, 0);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Error while feeding the filtergraph\n");
return ret;
}
/* pull filtered frames from the filtergraph */
while (1)
{
// AVFrame *filter_frame = av_frame_alloc();
// ret = av_buffersink_get_frame(filter->buffersink_ctx, filter_frame);
ret = av_buffersink_get_frame(filter->buffersink_ctx, filter->filtered_frame);
if (ret < 0)
{
/* if no more frames for output - returns AVERROR(EAGAIN)
* if flushed and no more frames for output - returns AVERROR_EOF
* rewrite retcode to 0 to show it as normal procedure completion
*/
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
// av_frame_free(&filter_frame);
ret = 0;
}
break;
}
// filter->filtered_frame = filter_frame;
filter->filtered_frame->pict_type = AV_PICTURE_TYPE_NONE;
ret = encode_write_frame(stream_index, 0);
av_frame_unref(filter->filtered_frame);
if (ret < 0)
break;
}
return ret;
}
void RTMPClient::send()
{
while (1)
{
AVPacket *output_packet;
if (!(output_packet = output_packet_queue_.Pop()))
{
Sleep(100);
if (writer_flag_.Get() == false)
break;
continue;
}
if (av_interleaved_write_frame(ofmt_ctx, output_packet) != 0)
{
av_log(NULL, AV_LOG_ERROR, "error in writing video frame\n");
}
// if (av_write_frame(ofmt_ctx, output_packet) != 0)
// {
// av_log(NULL, AV_LOG_ERROR, "error in writing %d-frame\n",output_packet->stream_index);
// }
av_packet_free(&output_packet);
}
int ret = av_write_trailer(ofmt_ctx);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "error in writing av trailer\n");
exit(1);
}
return;
}
int RTMPClient::init()
{
// av_log_set_level(AV_LOG_DEBUG);
int ret;
if ((ret = open_input_file()) < 0)
return ret;
if ((ret = open_output_file()) < 0)
return ret;
if (audio_index != -1)
{
// /* Initialize the resampler to be able to convert audio sample formats. */
// if (init_resampler(stream_ctx[audio_index].dec_ctx, stream_ctx[audio_index].enc_ctx,
// &resample_context))
// goto end;
/* Initialize the FIFO buffer to store audio samples to be encoded. */
if ((ret = init_fifo(&fifo, scs[audio_index].enc_ctx)) < 0)
return ret;
}
if ((ret = init_filters()) < 0)
return ret;
return ret;
}
int RTMPClient::run()
{
int ret = 0;
start_time = av_gettime();
writer_flag_.Set(true);
std::thread sendThread(&RTMPClient::send, this);
sendThread.detach();
/* read all packets */
AVPacket *packet = NULL;
unsigned int stream_index;
if (!(packet = av_packet_alloc()))
return ret;
while (1)
{
if ((ret = av_read_frame(ifmt_ctx, packet)) < 0)
{
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
av_log(NULL, AV_LOG_ERROR, "[PZX] av_read_frame exit:%d\n", ret);
break;
}
// calculate pts and dts
if (packet->pts == AV_NOPTS_VALUE)
{
av_log(NULL, AV_LOG_ERROR, "[PZX] packet dts is null error10\n");
// AVRational time_base = ifmt_ctx->streams[video_index]->time_base;
// int64_t cal_duration = (int64_t)(AV_TIME_BASE /
// av_q2d(ifmt_ctx->streams[video_index]->r_frame_rate));
// packet.pts = (int64_t)((frame_index * cal_duration) /
// (av_q2d(time_base) * AV_TIME_BASE));
// packet.dts = packet.pts;
// packet.duration = (int64_t)(cal_duration / (av_q2d(time_base) * AV_TIME_BASE));
}
stream_index = packet->stream_index;
// av_log(NULL, AV_LOG_INFO, "read stream_%u\t pts:%d\t dts:%d\r", stream_index, packet->pts, packet->dts);
if (filter_ctx[stream_index].filter_graph)
{
RTMPClient::StreamContext *sc = &scs[stream_index];
#if 1
AVRational instream_timebase = ifmt_ctx->streams[stream_index]->time_base;
// AVRational outstream_timebase = ofmt_ctx->streams[stream_index]->time_base;
AVRational dec_ctx_timebase = sc->dec_ctx->time_base; //解码器的时间基
// 时间基转换,视频在编码前需要修改paket的pts等数字为framerate;不然就是有问题的
AVRational raw_video_time_base = av_inv_q(sc->dec_ctx->framerate); //视频解码器需要取framerate
//官方例子有bug,视频的时间轴需要改成framerate,不然会视频慢一倍
//解出来的封装需要重新定义时间轴
av_packet_rescale_ts(packet, instream_timebase, stream_index == vedio_index ? raw_video_time_base : dec_ctx_timebase);
// av_log(NULL, AV_LOG_INFO, "[PZX] read2 packet stream_index:%d packet pts:%d dts:%d duration:%d\n", packet->stream_index, packet->pts, packet->dts, packet->duration);
#endif
ret = avcodec_send_packet(sc->dec_ctx, packet);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");
break;
}
//初始化过滤器
while (ret >= 0)
{
ret = avcodec_receive_frame(sc->dec_ctx, sc->dec_frame);
if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
break;
else if (ret < 0)
return ret;
//需要设置吗?
sc->dec_frame->pts = sc->dec_frame->best_effort_timestamp;
ret = filter_encode_write_frame(sc->dec_frame, stream_index);
if (ret < 0)
return ret;
}
}
else
{
av_log(NULL, AV_LOG_ERROR, "[pzx] bu neng jin error. no way\n");
/* remux this frame without reencoding */
av_packet_rescale_ts(packet,
ifmt_ctx->streams[stream_index]->time_base,
ofmt_ctx->streams[stream_index]->time_base);
ret = av_interleaved_write_frame(ofmt_ctx, packet);
if (ret < 0)
return ret;
}
av_packet_unref(packet);
// av_packet_free(&packet);
}
av_packet_free(&packet);
int i;
/* flush filters and encoders */
for (i = 0; i < ifmt_ctx->nb_streams; i++)
{
/* flush filter */
if (!filter_ctx[i].filter_graph)
continue;
ret = filter_encode_write_frame(NULL, i);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "Flushing filter failed\n");
return ret;
}
/* flush encoder */
if (!(scs[i].enc_ctx->codec->capabilities & AV_CODEC_CAP_DELAY))
{
continue;
}
if ((ret = encode_write_frame(stream_index, 1)) < 0)
{
av_log(NULL, AV_LOG_ERROR, "Flushing encoder failed\n");
return ret;
}
}
av_write_trailer(ofmt_ctx);
return ret ? 1 : 0;
}
int main()
{
RTMPClient clinet;
int ret = -1;
if ((ret = clinet.init()) < 0)
return ret;
ret = clinet.run();
return ret;
}
transcodingRtmpThread1.h
#define __STDC_CONSTANT_MACROS
extern "C"
{
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
}
#include
#include
#include
#include
#define USE_QUEUE 0 //使用队列开关
#define USE_CAM 1 //摄像头/文件推流切换开关
#define OPENLOG 0 //日志输出开关,对推流效率有影响,先关闭
#define USE_RTMP 1 //如果要播放文件到RTMP,需要控制速率
#include
#include "thread_safe_data.hpp"
using namespace std;
class RTMPClient
{
public:
RTMPClient() {}
~RTMPClient();
ThreadSafeQueue video_decoded_frame_queue_, video_scaled_frame_queue_;
ThreadSafeQueue audio_decoded_frame_queue_, audio_resampled_frame_queue_;
ThreadSafeQueue output_packet_queue_;
ThreadSafeFlag writer_flag_;
AVFormatContext *ifmt_ctx;
AVFormatContext *ofmt_ctx;
int inputFormatContext = 0;
SwrContext *resample_context = NULL;
AVAudioFifo *fifo = NULL;
int frameIndex = 0;
int64_t last_dts=0;
int64_t current_a_frameIndex = 0;
int64_t current_v_frameIndex = 0;
int64_t a_frameIndex = 0;
int64_t v_frameIndex = 0;
clock_t audio_start_time=0;
clock_t video_start_time=0;
int64_t v_start_dts = 0;
clock_t lastClock=clock();
int64_t start_time;
int audio_index = -1;
int vedio_index = -1;
long long dts_index = 0;
void printts(AVPacket *packet){
av_log(NULL, AV_LOG_INFO, "read stream_%u\t pts:%d\t dts:%d\r", packet->stream_index, packet->pts, packet->dts);
}
void log_packet(const AVFormatContext *fmt_ctx, AVPacket *pkt)
{
#if OPENLOG
AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base;
printf("%d-pts:%d dts:%d \n", pkt->stream_index, pkt->pts, pkt->dts);
// printf("%d-pts:%s pts_time:%s dts:%s dts_time:%s duration:%s duration_time:%s\n",
// pkt->stream_index, pzx_av_ts2str(pkt->pts), pzx_ts2timestr(pkt->pts, time_base),
// pzx_av_ts2str(pkt->dts), pzx_ts2timestr(pkt->dts, time_base),
// pzx_av_ts2str(pkt->duration), pzx_ts2timestr(pkt->duration, time_base));
#endif
}
struct FilteringContext
{
AVFilterContext *buffersink_ctx;
AVFilterContext *buffersrc_ctx;
AVFilterGraph *filter_graph;
AVPacket *enc_pkt;
AVFrame *filtered_frame;
};
FilteringContext *filter_ctx;
struct StreamContext
{
AVCodecContext *dec_ctx;
AVCodecContext *enc_ctx;
AVStream *in_stream;
AVStream *out_stream;
int next_pts; //下一帧序号
int last_dts = -1;
int offset_dts = -1;
int currentTimePoint;
AVFrame *dec_frame;
AVFrame *audio_frame;
};
StreamContext *scs; //分别代表音频和视频包装对象
/**
* Initialize a FIFO buffer for the audio samples to be encoded.
* @param[out] fifo Sample buffer
* @param output_codec_context Codec context of the output file
* @return Error code (0 if successful)
*/
static int init_fifo(AVAudioFifo **fifo, AVCodecContext *output_codec_context)
{
/* Create the FIFO buffer based on the specified output sample format. */
if (!(*fifo = av_audio_fifo_alloc(output_codec_context->sample_fmt,
output_codec_context->channels, 1)))
{
fprintf(stderr, "Could not allocate FIFO\n");
return AVERROR(ENOMEM);
}
return 0;
}
int putfifo(AVFrame *frame, AVCodecContext *enc_ctx)
{
if (av_audio_fifo_size(fifo) < (enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024))
{
// av_log(NULL, AV_LOG_ERROR, "[pzx] av_audio_fifo_size EOF\n");
return -1;
}
frame = av_frame_alloc();
frame->nb_samples = enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024;
frame->channel_layout = enc_ctx->channel_layout;
frame->format = enc_ctx->sample_fmt;
frame->channels = av_get_default_channel_layout(enc_ctx->channel_layout);
frame->sample_rate = enc_ctx->sample_rate;
if (frame->nb_samples)
{
int error = 0;
if ((error = av_frame_get_buffer(frame, 0)) < 0)
{
// av_log(NULL, AV_LOG_ERROR, "[pzx] frame->nb_samples size %d\n", frame->nb_samples);
av_frame_free(&frame);
return error;
}
int ret = av_audio_fifo_read(fifo, (void **)frame->data, (enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024));
#if OPENLOG
av_log(NULL, AV_LOG_INFO, "[pzx] ret:%d ,fifo size:%d,frame->nb_samples size:%d\n", ret, av_audio_fifo_size(fifo), frame->nb_samples);
#endif
if (ret < 0)
{
fprintf(stderr, "Error read audio buffer\n");
return -1;
}
ret = av_frame_make_writable(frame);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "av_frame_make_writable failed");
return -1;
}
return ret;
}
else
{
return -1; //继续?
}
}
int putfifoOld(AVFrame *frame, AVCodecContext *enc_ctx)
{
if (frame)
{
av_frame_free(&frame);
}
frame = av_frame_alloc();
if (av_audio_fifo_size(fifo) < (enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024))
{
// av_log(NULL, AV_LOG_ERROR, "[pzx] av_audio_fifo_size EOF\n");
return -1;
}
frame->nb_samples = enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024;
frame->channel_layout = enc_ctx->channel_layout;
frame->format = enc_ctx->sample_fmt;
// frame->channels = av_get_default_channel_layout(enc_ctx->channel_layout);
frame->sample_rate = enc_ctx->sample_rate;
if (frame->nb_samples)
{
int error = 0;
if ((error = av_frame_get_buffer(frame, 0)) < 0)
{
// av_log(NULL, AV_LOG_ERROR, "[pzx] frame->nb_samples size %d\n", frame->nb_samples);
av_frame_free(&frame);
return error;
}
int ret = av_audio_fifo_read(fifo, (void **)frame->data, (enc_ctx->frame_size > 0 ? enc_ctx->frame_size : 1024));
#if OPENLOG
av_log(NULL, AV_LOG_INFO, "[pzx] ret:%d ,fifo size:%d,frame->nb_samples size:%d\n", ret, av_audio_fifo_size(fifo), frame->nb_samples);
#endif
if (ret < 0)
{
fprintf(stderr, "Error read audio buffer\n");
return -1;
}
ret = av_frame_make_writable(frame);
if (ret < 0)
{
av_log(NULL, AV_LOG_ERROR, "av_frame_make_writable failed");
return -1;
}
return ret;
}
else
{
return 0; //继续?
}
}
int init_filters(void);
int init_filter(FilteringContext *fctx, AVCodecContext *dec_ctx,
AVCodecContext *enc_ctx, const char *filter_spec);
int open_input_file();
int open_output_file();
int WriteFrame(AVFrame *frame);
int encode_write_frame(unsigned int stream_index, int flush);
int filter_encode_write_frame(AVFrame *frame, unsigned int stream_index);
int init();
void send();
int run();
};
thread_safe_data.hpp
// #ifndef __THREAD_SAFE_QUEUE_HPP__
// #define __THREAD_SAFE_QUEUE_HPP__
extern "C" {
#include
#include
#include
#include
#include
#include
#include
#include
}
#include
#include
#include
#include
template
class ThreadSafeQueue {
public:
ThreadSafeQueue() {};
void Push(T pFrame) {
mutex_.lock();
list_.emplace_back(pFrame);
mutex_.unlock();
}
T Pop() {
T ret = NULL;
if (!list_.empty()) {
mutex_.lock();
ret = list_.front();
list_.pop_front();
mutex_.unlock();
}
return ret;
}
int Size() {
int ret = -1;
mutex_.lock();
ret = list_.size();
mutex_.unlock();
return ret;
}
private:
std::list list_;
std::mutex mutex_;
};
class ThreadSafeFlag {
public:
void Set(bool value) {
mutex_.lock();
flag = value;
mutex_.unlock();
}
bool Get() {
bool ret = false;
mutex_.lock();
ret = flag;
mutex_.unlock();
return ret;
}
private:
bool flag = true;
std::mutex mutex_;
};
// #endif