修改ffplay官方代码,使用ffmpeg实现从新编码保存mp4功能。调用avcodec_receive_packet返回-1,提示Resource temporarily unavailable,保存出来的文件没有I帧。
完整代码:
https://github.com/hrdzkj/ffplay/tree/ffplayOpengl
(注意是在ffplayOpengl分支)
关键代码:
int ffp_start_record(FFPlayer *ffp, const char *file_name)
{
assert(ffp);
VideoState *is = ffp->is;
if (!file_name || !strlen(file_name)) { // 没有路径
av_log(ffp, AV_LOG_ERROR, "filename is invalid");
goto end;
}
if (!is || !is->ic || is->paused || is->abort_request) { // 没有上下文,或者上下文已经停止
av_log(ffp, AV_LOG_ERROR, "is,is->ic,is->paused is invalid");
goto end;
}
if (ffp->is_record) { // 已经在录制
av_log(ffp, AV_LOG_ERROR, "recording has started");
goto end;
}
ffp->m_ofmt_ctx = NULL;
ffp->is_record = 0;
ffp->record_error = 0;
// todo avformat_new_stream,avcodec_copy_context/avcodec_parameters_from_context 等api函数的作用,初始化了什么东西
// 初始化一个用于输出的AVFormatContext结构体
avformat_alloc_output_context2(&ffp->m_ofmt_ctx, NULL, "mp4", file_name);
if (!ffp->m_ofmt_ctx) {
av_log(ffp, AV_LOG_ERROR, "Could not create output context filename is %s\n", file_name);
goto end;
}
stream_ctx = av_mallocz_array(is->ic->nb_streams, sizeof(*stream_ctx));
if (!stream_ctx) {
goto end;
}
for (int i = 0; i < is->ic->nb_streams; i++) {
int ret;
AVCodec *encoder;
AVCodecContext *enc_ctx;
AVCodecContext *dec_ctx = is->ic->streams[i]->codec;
if (dec_ctx->codec_type != AVMEDIA_TYPE_VIDEO && dec_ctx->codec_type != AVMEDIA_TYPE_AUDIO) {
continue;
}
//对照输入流创建输出流通道
AVStream *in_stream = is->ic->streams[i];
AVStream *out_stream = avformat_new_stream(ffp->m_ofmt_ctx, NULL);
if (!out_stream) {
av_log(ffp, AV_LOG_ERROR, "Failed allocating output stream\n");
goto end;
}
//查找编码器、创建编码器上下文、设置编码器参数,然后打开编码器
encoder = avcodec_find_encoder(dec_ctx->codec_id);
if (!encoder) {
av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n");
goto end;
}
enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx) {
av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
goto end;
}
//不是帧率的问题,是I P B帧的区别导致的问题了
ret = avcodec_parameters_to_context(enc_ctx, in_stream->codecpar);
if (ret < 0) {
printf("Failed to copy context input to output stream codec context\n");
goto end;
}
if (enc_ctx->codec_type == AVMEDIA_TYPE_VIDEO) {
//enc_ctx->height = dec_ctx->height;
//enc_ctx->width = dec_ctx->width;
//enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio;
//enc_ctx->gop_size = 10;
//enc_ctx->max_b_frames = 0;
//if (encoder->pix_fmts)
//enc_ctx->pix_fmt = encoder->pix_fmts[0];
//else
// enc_ctx->pix_fmt = dec_ctx->pix_fmt;
enc_ctx->time_base = av_inv_q(dec_ctx->framerate);
//enc_ctx->time_base = //(AVRational) { 1, 30 };
//enc_ctx->framerate = //(AVRational) { 30, 1 };
}
else {
//enc_ctx->sample_rate = dec_ctx->sample_rate;
//enc_ctx->channel_layout = dec_ctx->channel_layout;
//enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
//enc_ctx->sample_fmt = encoder->sample_fmts[0];
enc_ctx->time_base = (AVRational) { 1, dec_ctx->sample_rate };
}
if (ffp->m_ofmt_ctx->flags & AVFMT_GLOBALHEADER) {
enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
ret = avcodec_open2(enc_ctx, encoder, NULL);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i);
goto end;
}
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
//ret = avcodec_parameters_to_context(out_stream->codec, in_stream->codecpar);
//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);
goto end;
}
out_stream->time_base = enc_ctx->time_base;
stream_ctx[i].enc_ctx = enc_ctx;
}
av_dump_format(ffp->m_ofmt_ctx, 0, file_name, 1);
// 打开输出文件
if (!(ffp->m_ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
if (avio_open(&ffp->m_ofmt_ctx->pb, file_name, AVIO_FLAG_WRITE) < 0) {
av_log(ffp, AV_LOG_ERROR, "Could not open output file '%s'", file_name);
goto end;
}
}
// 写视频文件头
if (avformat_write_header(ffp->m_ofmt_ctx, NULL) < 0) {
av_log(ffp, AV_LOG_ERROR, "Error occurred when opening output file\n");
goto end;
}
ffp->is_first = 0;
ffp->is_record = 1;
ffp->record_error = 0;
return 0;
end:
ffp->record_error = 1;
return -1;
}
static void encode(FFPlayer *ffp,AVCodecContext *enc_ctx, AVFrame *frame)
{
int ret;
int got_frame;// malloc(sizeof(int));
AVPacket pkt = { 0 };
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
int stream_index=getMediaTypeStreamIndex(ffp,AVMEDIA_TYPE_VIDEO);
/* send the frame to the encoder */
if (frame)
printf("Send frame %3"PRId64"\n", frame->pts);
/*
//旧的api
ret= avcodec_encode_video2(enc_ctx, &pkt,frame, &got_frame);
if (ret < 0)
return ret;
if (!(got_frame)) {
fprintf(stderr, "avcodec_encode_video2 fail \n");
return ;
}
ffp_record_file(ffp, &pkt);
av_packet_unref(&pkt);
*/
//新的api
ret = avcodec_send_frame(enc_ctx, frame);
if (ret < 0) {
fprintf(stderr, "Error sending a frame for encoding\n");
return -1;
}
while (1) {
ret = avcodec_receive_packet(enc_ctx, &pkt);
if (ret) {
fprintf(stderr, "Error encoding ret= %d,%s \n", ret,av_err2str(ret));
break;
}
fprintf(stdout, " -----> encoding success!\n");
pkt.stream_index = stream_index;
ffp_record_file(ffp,&pkt);
av_packet_unref(&pkt);
}
}
提示Resource temporarily unavailable的截图
生成的mp4文件无I帧截图