ffmpeg源码优化之推流发送篇

1.引言

大家好,距离上篇文章已经过去有一段时间了,主要是最近太忙了,一直没有更新。今天总算能抽出一点时间,说说ffmpeg源码级别的优化了,这块应该会连载,请大家持续关注。废话不多说,接下来就进入正题,说说怎么样进行优化。

2.问题引出及解决

使用ffmpeg接口去推流,当网络不稳定时,av_interleaved_write_frame有时候会出现长期无法返回,也就是卡死在里面,造成无法退出或不能进行下一步处理。下面我们就来分析下该接口究竟干了什么,揭开其神秘的面纱。

int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt)
{
    int ret, flush = 0;

    ret = prepare_input_packet(s, pkt);
    if (ret < 0)
        goto fail;

    if (pkt) {
        AVStream *st = s->streams[pkt->stream_index];

        ret = do_packet_auto_bsf(s, pkt);
        if (ret == 0)
            return 0;
        else if (ret < 0)
            goto fail;

        if (s->debug & FF_FDEBUG_TS)
            av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame size:%d dts:%s pts:%s\n",
                pkt->size, av_ts2str(pkt->dts), av_ts2str(pkt->pts));

#if FF_API_COMPUTE_PKT_FIELDS2 && FF_API_LAVF_AVCTX
        if ((ret = compute_muxer_pkt_fields(s, st, pkt)) < 0 && !(s->oformat->flags & AVFMT_NOTIMESTAMPS))
            goto fail;
#endif

        if (pkt->dts == AV_NOPTS_VALUE && !(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
            ret = AVERROR(EINVAL);
            goto fail;
        }
    } else {
        av_log(s, AV_LOG_TRACE, "av_interleaved_write_frame FLUSH\n");
        flush = 1;
    }

    for (;; ) {
        AVPacket opkt;
        int ret = interleave_packet(s, &opkt, pkt, flush);
        if (pkt) {
            memset(pkt, 0, sizeof(*pkt));
            av_init_packet(pkt);
            pkt = NULL;
        }
        if (ret <= 0) //FIXME cleanup needed for ret<0 ?
            return ret;

        ret = write_packet(s, &opkt);
        if (ret >= 0)
            s->streams[opkt.stream_index]->nb_frames++;

        av_packet_unref(&opkt);

        if (ret < 0)
            return ret;
        if(s->pb && s->pb->error)
            return s->pb->error;
    }
fail:
    av_packet_unref(pkt);
    return ret;
}

上面就是发送封装包的源代码,下面来一一分析。

static int prepare_input_packet(AVFormatContext *s, AVPacket *pkt)
{
    int ret;
	/*数据包是否合格*/
    ret = check_packet(s, pkt);
    if (ret < 0)
        return ret;

#if !FF_API_COMPUTE_PKT_FIELDS2 || !FF_API_LAVF_AVCTX
    /* 清除时间戳 */
    if (!(s->oformat->flags & AVFMT_NOTIMESTAMPS)) {
        AVStream *st = s->streams[pkt->stream_index];

        /* 当没有重新排序时,也就是pts等于dts,则需要把pts和dts分别进行设置*/
        if (!st->internal->reorder) {
            if (pkt->pts == AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE)
                pkt->pts = pkt->dts;
            if (pkt->dts == AV_NOPTS_VALUE && pkt->pts != AV_NOPTS_VALUE)
                pkt->dts = pkt->pts;
        }

        /* 检查是否设置了时间戳 */
        if (pkt->pts == AV_NOPTS_VALUE || pkt->dts == AV_NOPTS_VALUE) {
            av_log(s, AV_LOG_ERROR,
                   "Timestamps are unset in a packet for stream %d\n", st->index);
            return AVERROR(EINVAL);
        }

        /*检查dts是否增加,如果dts没有增加,则需要去检查是否在封包时有其它异常*/
        if (st->cur_dts != AV_NOPTS_VALUE &&
            ((!(s->oformat->flags & AVFMT_TS_NONSTRICT) && st->cur_dts >= pkt->dts) ||
             st->cur_dts > pkt->dts)) {
            av_log(s, AV_LOG_ERROR,
                   "Application provided invalid, non monotonically increasing "
                   "dts to muxer in stream %d: %" PRId64 " >= %" PRId64 "\n",
                   st->index, st->cur_dts, pkt->dts);
            return AVERROR(EINVAL);
        }
			/*
      如果pts小于dts,则视为异常,因为dts是解码时间戳,pts是显示时间戳
      正常情况是,先解码,后显示,所以pts应该是要大于dts,这样才会合理。
      */
        if (pkt->pts < pkt->dts) {
            av_log(s, AV_LOG_ERROR, "pts %" PRId64 " < dts %" PRId64 " in stream %d\n",
                   pkt->pts, pkt->dts, st->index);
            return AVERROR(EINVAL);
        }
    }
#endif

    return 0;
}

static int do_packet_auto_bsf(AVFormatContext *s, AVPacket *pkt) {
    AVStream *st = s->streams[pkt->stream_index];
    int i, ret;

    if (!(s->flags & AVFMT_FLAG_AUTO_BSF))
        return 1;
	/*检查是否设置了码流过滤,以及额外数据提取*/
    if (s->oformat->check_bitstream) {
        if (!st->internal->bitstream_checked) {
            if ((ret = s->oformat->check_bitstream(s, pkt)) < 0)
                return ret;
            else if (ret == 1)
                st->internal->bitstream_checked = 1;
        }
    }

#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
    if (st->internal->nb_bsfcs) {
      /*分离出头部数据*/
        ret = av_packet_split_side_data(pkt);
        if (ret < 0)
            av_log(s, AV_LOG_WARNING, "Failed to split side data before bitstream filter\n");
    }
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    for (i = 0; i < st->internal->nb_bsfcs; i++) {
        AVBSFContext *ctx = st->internal->bsfcs[i];
        /*当任何过滤器需要在流结束时刷新,则我们需要在刷新整个BSF数据链表*/
        if ((ret = av_bsf_send_packet(ctx, pkt)) < 0) {
            av_log(ctx, AV_LOG_ERROR,
                    "Failed to send packet to filter %s for stream %d\n",
                    ctx->filter->name, pkt->stream_index);
            return ret;
        }
        /*当有多个码流过滤器,需要循环去生成,并调用*/
        if ((ret = av_bsf_receive_packet(ctx, pkt)) < 0) {
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                return 0;
            av_log(ctx, AV_LOG_ERROR,
                    "Failed to send packet to filter %s for stream %d\n",
                    ctx->filter->name, pkt->stream_index);
            return ret;
        }
    }
    return 1;
}

下面就是开始循环发包的过程,详细分析如下:

/*
	功能:间隔发送数据包,实际上是缓存了。这也是av_interleaved_write_frame与av_write_frame的区别之处
  AVPacket *out:表述输出数据包
  AVPacket *in:表示输入数据包
  flush:如果为1表示输入没有更多数据包,应该输出剩余数据包
  返回值:1表示输出数据包。0:表示没有数据包
  <0,表示发生错误
*/
static int interleave_packet(AVFormatContext *s, AVPacket *out, AVPacket *in, int flush)
{
    if (s->oformat->interleave_packet) {
        int ret = s->oformat->interleave_packet(s, out, in, flush);
        if (in)
            av_packet_unref(in);
        return ret;
    } else
      /*把数据包缓存到队列中,那什么时候发送?也就是flush什么时候为1呢?*/
        return ff_interleave_packet_per_dts(s, out, in, flush);
}

int ff_interleave_packet_per_dts(AVFormatContext *s, AVPacket *out,
                                 AVPacket *pkt, int flush)
{
    AVPacketList *pktl;
    int stream_count = 0;
    int noninterleaved_count = 0;
    int i, ret;
    int eof = flush;

    if (pkt) {
      /*比较数据包的dts添加数据包到想要队列中*/
        if ((ret = ff_interleave_add_packet(s, pkt, interleave_compare_dts)) < 0)
            return ret;
    }

    for (i = 0; i < s->nb_streams; i++) {
        if (s->streams[i]->last_in_packet_buffer) {
          /*记录数据包个数*/
            ++stream_count;
        } else if (s->streams[i]->codecpar->codec_type != AVMEDIA_TYPE_ATTACHMENT &&
                   s->streams[i]->codecpar->codec_id != AV_CODEC_ID_VP8 &&
                   s->streams[i]->codecpar->codec_id != AV_CODEC_ID_VP9) {
            ++noninterleaved_count;
        }
    }
	/*表示缓冲区已满,需要输出*/
    if (s->internal->nb_interleaved_streams == stream_count)
        flush = 1;

    if (s->max_interleave_delta > 0 &&
        s->internal->packet_buffer &&
        !flush &&
        s->internal->nb_interleaved_streams == stream_count+noninterleaved_count
    ) {
        AVPacket *top_pkt = &s->internal->packet_buffer->pkt;
        int64_t delta_dts = INT64_MIN;
        int64_t top_dts = av_rescale_q(top_pkt->dts,
                                       s->streams[top_pkt->stream_index]->time_base,
                                       AV_TIME_BASE_Q);

        for (i = 0; i < s->nb_streams; i++) {
            int64_t last_dts;
            const AVPacketList *last = s->streams[i]->last_in_packet_buffer;

            if (!last)
                continue;

            last_dts = av_rescale_q(last->pkt.dts,
                                    s->streams[i]->time_base,
                                    AV_TIME_BASE_Q);
            delta_dts = FFMAX(delta_dts, last_dts - top_dts);
        }
			/*缓冲区中最老与最新数据包的dts间隔,大于了最大间隔,则需要输出数据*/
        if (delta_dts > s->max_interleave_delta) {
            av_log(s, AV_LOG_DEBUG,
                   "Delay between the first packet and last packet in the "
                   "muxing queue is %"PRId64" > %"PRId64": forcing output\n",
                   delta_dts, s->max_interleave_delta);
            flush = 1;
        }
    }

    if (s->internal->packet_buffer &&
        eof &&
        (s->flags & AVFMT_FLAG_SHORTEST) &&
        s->internal->shortest_end == AV_NOPTS_VALUE) {
        AVPacket *top_pkt = &s->internal->packet_buffer->pkt;

        s->internal->shortest_end = av_rescale_q(top_pkt->dts,
                                       s->streams[top_pkt->stream_index]->time_base,
                                       AV_TIME_BASE_Q);
    }

    if (s->internal->shortest_end != AV_NOPTS_VALUE) {
        while (s->internal->packet_buffer) {
            AVPacket *top_pkt = &s->internal->packet_buffer->pkt;
            AVStream *st;
            int64_t top_dts = av_rescale_q(top_pkt->dts,
                                        s->streams[top_pkt->stream_index]->time_base,
                                        AV_TIME_BASE_Q);

            if (s->internal->shortest_end + 1 >= top_dts)
                break;

            pktl = s->internal->packet_buffer;
            st   = s->streams[pktl->pkt.stream_index];

            s->internal->packet_buffer = pktl->next;
            if (!s->internal->packet_buffer)
                s->internal->packet_buffer_end = NULL;

            if (st->last_in_packet_buffer == pktl)
                st->last_in_packet_buffer = NULL;

            av_packet_unref(&pktl->pkt);
            av_freep(&pktl);
            flush = 0;
        }
    }

    if (stream_count && flush) {
        AVStream *st;
        pktl = s->internal->packet_buffer;
        *out = pktl->pkt;
        st   = s->streams[out->stream_index];

        s->internal->packet_buffer = pktl->next;
        if (!s->internal->packet_buffer)
            s->internal->packet_buffer_end = NULL;

        if (st->last_in_packet_buffer == pktl)
            st->last_in_packet_buffer = NULL;
        av_freep(&pktl);

        return 1;
    } else {
        av_init_packet(out);
        return 0;
    }
}

前面做了数据包的合格性检查,pts与dts的检查,缓冲到队列中,并在合适的时机,去发送数据包。接下来就该主角上场了。

/**
 功能:发送数据包
 AVFormatContext *s:封装上下文
 AVPacket *pkt:需要发送的数据包
 */
static int write_packet(AVFormatContext *s, AVPacket *pkt)
{
    int ret, did_split;
    int64_t pts_backup, dts_backup;

    pts_backup = pkt->pts;
    dts_backup = pkt->dts;

  /*调整pts与dts时间偏移,这个偏移值是可以灵活调整*/
    if (s->output_ts_offset) {
        AVStream *st = s->streams[pkt->stream_index];
        int64_t offset = av_rescale_q(s->output_ts_offset, AV_TIME_BASE_Q, st->time_base);

        if (pkt->dts != AV_NOPTS_VALUE)
            pkt->dts += offset;
        if (pkt->pts != AV_NOPTS_VALUE)
            pkt->pts += offset;
    }
	/*根据时间戳的不同情况,调整时间戳,并进行时间基的转化*/
    if (s->avoid_negative_ts > 0) {
        AVStream *st = s->streams[pkt->stream_index];
        int64_t offset = st->mux_ts_offset;
        int64_t ts = s->internal->avoid_negative_ts_use_pts ? pkt->pts : pkt->dts;

        if (s->internal->offset == AV_NOPTS_VALUE && ts != AV_NOPTS_VALUE &&
            (ts < 0 || s->avoid_negative_ts == AVFMT_AVOID_NEG_TS_MAKE_ZERO)) {
            s->internal->offset = -ts;
            s->internal->offset_timebase = st->time_base;
        }

        if (s->internal->offset != AV_NOPTS_VALUE && !offset) {
            offset = st->mux_ts_offset =
                av_rescale_q_rnd(s->internal->offset,
                                 s->internal->offset_timebase,
                                 st->time_base,
                                 AV_ROUND_UP);
        }

        if (pkt->dts != AV_NOPTS_VALUE)
            pkt->dts += offset;
        if (pkt->pts != AV_NOPTS_VALUE)
            pkt->pts += offset;

        if (s->internal->avoid_negative_ts_use_pts) {
            if (pkt->pts != AV_NOPTS_VALUE && pkt->pts < 0) {
                av_log(s, AV_LOG_WARNING, "failed to avoid negative "
                    "pts %s in stream %d.\n"
                    "Try -avoid_negative_ts 1 as a possible workaround.\n",
                    av_ts2str(pkt->pts),
                    pkt->stream_index
                );
            }
        } else {
            av_assert2(pkt->dts == AV_NOPTS_VALUE || pkt->dts >= 0 || s->max_interleave_delta > 0);
            if (pkt->dts != AV_NOPTS_VALUE && pkt->dts < 0) {
                av_log(s, AV_LOG_WARNING,
                    "Packets poorly interleaved, failed to avoid negative "
                    "timestamp %s in stream %d.\n"
                    "Try -max_interleave_delta 0 as a possible workaround.\n",
                    av_ts2str(pkt->dts),
                    pkt->stream_index
                );
            }
        }
    }

#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
    did_split = av_packet_split_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif
	/*发送数据包的头部数据*/
    if (!s->internal->header_written) {
        ret = s->internal->write_header_ret ? s->internal->write_header_ret : write_header_internal(s);
        if (ret < 0)
            goto fail;
    }

    if ((pkt->flags & AV_PKT_FLAG_UNCODED_FRAME)) {
        AVFrame *frame = (AVFrame *)pkt->data;
        av_assert0(pkt->size == UNCODED_FRAME_PACKET_SIZE);
      /*发送没有编码的帧*/
        ret = s->oformat->write_uncoded_frame(s, pkt->stream_index, &frame, 0);
        av_frame_free(&frame);
    } else {
      /*根据不同的封装格式,调用不同发送数据包的函数
      这里是一个函数指针,把封装协议,注册到封装上下文,实际的调用就在Flvenc.c里面
      */
        ret = s->oformat->write_packet(s, pkt);
    }

    if (s->pb && ret >= 0) {
        flush_if_needed(s);
        if (s->pb->error < 0)
            ret = s->pb->error;
    }

fail:
#if FF_API_LAVF_MERGE_SD
FF_DISABLE_DEPRECATION_WARNINGS
    if (did_split)
        av_packet_merge_side_data(pkt);
FF_ENABLE_DEPRECATION_WARNINGS
#endif

    if (ret < 0) {
        pkt->pts = pts_backup;
        pkt->dts = dts_backup;
    }

    return ret;
}

/*这里就是把具体实现的各类函数,进行注册的地方,如发送头部,发送数据包,发送数据包尾部*/
AVOutputFormat ff_flv_muxer = {
    .name           = "flv",
    .long_name      = NULL_IF_CONFIG_SMALL("FLV (Flash Video)"),
    .mime_type      = "video/x-flv",
    .extensions     = "flv",
    .priv_data_size = sizeof(FLVContext),
    .audio_codec    = CONFIG_LIBMP3LAME ? AV_CODEC_ID_MP3 : AV_CODEC_ID_ADPCM_SWF,
    .video_codec    = AV_CODEC_ID_FLV1,
    .write_header   = flv_write_header,
    .write_packet   = flv_write_packet,
    .write_trailer  = flv_write_trailer,
    .codec_tag      = (const AVCodecTag* const []) {
                          flv_video_codec_ids, flv_audio_codec_ids, 0
                      },
    .flags          = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
                      AVFMT_TS_NONSTRICT,
    .priv_class     = &flv_muxer_class,
};

接下来就看看发送数据包的真正实现

static int flv_write_packet(AVFormatContext *s, AVPacket *pkt)
{
    AVIOContext *pb      = s->pb;
    AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
    FLVContext *flv      = s->priv_data;
    FLVStreamContext *sc = s->streams[pkt->stream_index]->priv_data;
    unsigned ts;
    int size = pkt->size;
    uint8_t *data = NULL;
    int flags = -1, flags_size, ret;
    int64_t cur_offset = avio_tell(pb);

    if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A ||
        par->codec_id == AV_CODEC_ID_VP6  || par->codec_id == AV_CODEC_ID_AAC)
        flags_size = 2;
    else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4)
        flags_size = 5;
    else
        flags_size = 1;

    if (par->codec_id == AV_CODEC_ID_AAC || par->codec_id == AV_CODEC_ID_H264
            || par->codec_id == AV_CODEC_ID_MPEG4) {
        int side_size = 0;
      /*获取头部数据*/
        uint8_t *side = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);
        if (side && side_size > 0 && (side_size != par->extradata_size || memcmp(side, par->extradata, side_size))) {
            av_free(par->extradata);
            par->extradata = av_mallocz(side_size + AV_INPUT_BUFFER_PADDING_SIZE);
            if (!par->extradata) {
                par->extradata_size = 0;
                return AVERROR(ENOMEM);
            }
            memcpy(par->extradata, side, side_size);
            par->extradata_size = side_size;
          /*通过网络IO发送头部数据*/
            flv_write_codec_header(s, par);
        }
    }

    if (flv->delay == AV_NOPTS_VALUE)
        flv->delay = -pkt->dts;

    if (pkt->dts < -flv->delay) {
        av_log(s, AV_LOG_WARNING,
               "Packets are not in the proper order with respect to DTS\n");
        return AVERROR(EINVAL);
    }

    ts = pkt->dts;

    if (s->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
        write_metadata(s, ts);
        s->event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED;
    }
	/*刷新以前的数据,并标记新的数据开始,实际上就是以一组GOP为主去发送,使用关键帧去做判断*/
    avio_write_marker(pb, av_rescale(ts, AV_TIME_BASE, 1000),
                      pkt->flags & AV_PKT_FLAG_KEY && (flv->video_par ? par->codec_type == AVMEDIA_TYPE_VIDEO : 1) ? AVIO_DATA_MARKER_SYNC_POINT : AVIO_DATA_MARKER_BOUNDARY_POINT);

    switch (par->codec_type) {
    case AVMEDIA_TYPE_VIDEO:
        avio_w8(pb, FLV_TAG_TYPE_VIDEO);

        flags = ff_codec_get_tag(flv_video_codec_ids, par->codec_id);

        flags |= pkt->flags & AV_PKT_FLAG_KEY ? FLV_FRAME_KEY : FLV_FRAME_INTER;
        break;
    case AVMEDIA_TYPE_AUDIO:
        flags = get_audio_flags(s, par);

        av_assert0(size);

        avio_w8(pb, FLV_TAG_TYPE_AUDIO);
        break;
    case AVMEDIA_TYPE_SUBTITLE:
    case AVMEDIA_TYPE_DATA:
        avio_w8(pb, FLV_TAG_TYPE_META);
        break;
    default:
        return AVERROR(EINVAL);
    }

    if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
        /* check if extradata looks like mp4 formatted */
        if (par->extradata_size > 0 && *(uint8_t*)par->extradata != 1)
            /*寻找nal单元的起始码,对于nal单元不理解的,可以看看我前面的文章*/
            if ((ret = ff_avc_parse_nal_units_buf(pkt->data, &data, &size)) < 0)
                return ret;
    } else if (par->codec_id == AV_CODEC_ID_AAC && pkt->size > 2 &&
               (AV_RB16(pkt->data) & 0xfff0) == 0xfff0) {
        if (!s->streams[pkt->stream_index]->nb_frames) {
        av_log(s, AV_LOG_ERROR, "Malformed AAC bitstream detected: "
               "use the audio bitstream filter 'aac_adtstoasc' to fix it "
               "('-bsf:a aac_adtstoasc' option with ffmpeg)\n");
        return AVERROR_INVALIDDATA;
        }
        av_log(s, AV_LOG_WARNING, "aac bitstream error\n");
    }

    /* check Speex packet duration */
    if (par->codec_id == AV_CODEC_ID_SPEEX && ts - sc->last_ts > 160)
        av_log(s, AV_LOG_WARNING, "Warning: Speex stream has more than "
                                  "8 frames per packet. Adobe Flash "
                                  "Player cannot handle this!\n");

    if (sc->last_ts < ts)
        sc->last_ts = ts;

    if (size + flags_size >= 1<<24) {
        av_log(s, AV_LOG_ERROR, "Too large packet with size %u >= %u\n",
               size + flags_size, 1<<24);
        return AVERROR(EINVAL);
    }

    avio_wb24(pb, size + flags_size);
    avio_wb24(pb, ts & 0xFFFFFF);
    avio_w8(pb, (ts >> 24) & 0x7F); // timestamps are 32 bits _signed_
    avio_wb24(pb, flv->reserved);
/*拼接字幕*/
    if (par->codec_type == AVMEDIA_TYPE_DATA ||
        par->codec_type == AVMEDIA_TYPE_SUBTITLE ) {
        int data_size;
        int64_t metadata_size_pos = avio_tell(pb);
        if (par->codec_id == AV_CODEC_ID_TEXT) {
            // legacy FFmpeg magic?
            avio_w8(pb, AMF_DATA_TYPE_STRING);
            put_amf_string(pb, "onTextData");
            avio_w8(pb, AMF_DATA_TYPE_MIXEDARRAY);
            avio_wb32(pb, 2);
            put_amf_string(pb, "type");
            avio_w8(pb, AMF_DATA_TYPE_STRING);
            put_amf_string(pb, "Text");
            put_amf_string(pb, "text");
            avio_w8(pb, AMF_DATA_TYPE_STRING);
            put_amf_string(pb, pkt->data);
            put_amf_string(pb, "");
            avio_w8(pb, AMF_END_OF_OBJECT);
        } else {
            // just pass the metadata through
            avio_write(pb, data ? data : pkt->data, size);
        }
        /* write total size of tag */
        data_size = avio_tell(pb) - metadata_size_pos;
        avio_seek(pb, metadata_size_pos - 10, SEEK_SET);
        avio_wb24(pb, data_size);
        avio_seek(pb, data_size + 10 - 3, SEEK_CUR);
        avio_wb32(pb, data_size + 11);
    } else {
        av_assert1(flags>=0);
        avio_w8(pb,flags);
        if (par->codec_id == AV_CODEC_ID_VP6)
            avio_w8(pb,0);
        if (par->codec_id == AV_CODEC_ID_VP6F || par->codec_id == AV_CODEC_ID_VP6A) {
            if (par->extradata_size)
                avio_w8(pb, par->extradata[0]);
            else
                avio_w8(pb, ((FFALIGN(par->width,  16) - par->width) << 4) |
                             (FFALIGN(par->height, 16) - par->height));
        } else if (par->codec_id == AV_CODEC_ID_AAC)
          //拼接音频数据
            avio_w8(pb, 1); // AAC raw
        else if (par->codec_id == AV_CODEC_ID_H264 || par->codec_id == AV_CODEC_ID_MPEG4) {
          //拼接AVC的NALU单元  
          avio_w8(pb, 1); // AVC NALU
            avio_wb24(pb, pkt->pts - pkt->dts);
        }
			/*通过网络发送正真的音视频数据*/
        avio_write(pb, data ? data : pkt->data, size);

        avio_wb32(pb, size + flags_size + 11); // previous tag size
        flv->duration = FFMAX(flv->duration,
                              pkt->pts + flv->delay + pkt->duration);
    }

    if (flv->flags & FLV_ADD_KEYFRAME_INDEX) {
        switch (par->codec_type) {
            case AVMEDIA_TYPE_VIDEO:
                flv->videosize += (avio_tell(pb) - cur_offset);
                flv->lasttimestamp = flv->acurframeindex / flv->framerate;
                if (pkt->flags & AV_PKT_FLAG_KEY) {
                    double ts = flv->acurframeindex / flv->framerate;
                    int64_t pos = cur_offset;

                    flv->lastkeyframetimestamp = flv->acurframeindex / flv->framerate;
                    flv->lastkeyframelocation = pos;
                    flv_append_keyframe_info(s, flv, ts, pos);
                }
                flv->acurframeindex++;
                break;

            case AVMEDIA_TYPE_AUDIO:
                flv->audiosize += (avio_tell(pb) - cur_offset);
                break;

            default:
                av_log(s, AV_LOG_WARNING, "par->codec_type is type = [%d]\n", par->codec_type);
                break;
        }
    }

    av_free(data);

    return pb->error;
}

通过上面分析,通过网络发送数据到服务器,主要就是通过avio_write(pb, data ? data : pkt->data, size);所以下面继续详细剖析这个网络发送函数。

void avio_write(AVIOContext *s, const unsigned char *buf, int size)
{
  /*不通过缓冲区,直接发送数据*/
    if (s->direct && !s->update_checksum) {
        avio_flush(s);
      /*发数据*/
        writeout(s, buf, size);
        return;
    }
  /*通过缓冲区发数据*/
    while (size > 0) {
        int len = FFMIN(s->buf_end - s->buf_ptr, size);
        memcpy(s->buf_ptr, buf, len);
        s->buf_ptr += len;

        if (s->buf_ptr >= s->buf_end)
          /*本质还是调用writeout*/
            flush_buffer(s);

        buf += len;
        size -= len;
    }
}

static void writeout(AVIOContext *s, const uint8_t *data, int len)
{
    if (!s->error) {
        int ret = 0;
        if (s->write_data_type)
            ret = s->write_data_type(s->opaque, (uint8_t *)data,
                                     len,
                                     s->current_type,
                                     s->last_time);
        else if (s->write_packet)
      	/*使用不同协议的接口发送数据*/
            ret = s->write_packet(s->opaque, (uint8_t *)data, len);
        if (ret < 0) {
            s->error = ret;
        } else {
            if (s->pos + len > s->written)
                s->written = s->pos + len;
        }
    }
    if (s->current_type == AVIO_DATA_MARKER_SYNC_POINT ||
        s->current_type == AVIO_DATA_MARKER_BOUNDARY_POINT) {
        s->current_type = AVIO_DATA_MARKER_UNKNOWN;
    }
    s->last_time = AV_NOPTS_VALUE;
    s->writeout_count ++;
    s->pos += len;
}

s->write_packet(s->opaque, (uint8_t *)data, len),实际上这也是一个函数指针,使用不同的协议来发送数据,我这里就以Rtmp协议为例子。

int ffurl_write(URLContext *h, const unsigned char *buf, int size)
{
    if (!(h->flags & AVIO_FLAG_WRITE))
        return AVERROR(EIO);
    /* 避免发送太大包 */
    if (h->max_packet_size && size > h->max_packet_size)
        return AVERROR(EIO);
/*这里会使用具体的协议去发送,如Rtmp发送数据*/
    return retry_transfer_wrapper(h, (unsigned char *)buf, size, size,
                                  (int (*)(struct URLContext *, uint8_t *, int))
                                  h->prot->url_write);
}

static inline int retry_transfer_wrapper(URLContext *h, uint8_t *buf,
                                         int size, int size_min,
                                         int (*transfer_func)(URLContext *h,
                                                              uint8_t *buf,
                                                              int size))
{
    int ret, len;
    int fast_retries = 5;
    int64_t wait_since = 0;

    len = 0;
    while (len < size_min) {
        if (ff_check_interrupt(&h->interrupt_callback))
            return AVERROR_EXIT;
      /*回调Rtmp协议发送*/
        ret = transfer_func(h, buf + len, size - len);
        if (ret == AVERROR(EINTR))
            continue;
        if (h->flags & AVIO_FLAG_NONBLOCK)
            return ret;
        if (ret == AVERROR(EAGAIN)) {
            ret = 0;
            if (fast_retries) {
                fast_retries--;
            } else {
              /*默认没有设置超时时间,默认是一直阻塞,所以当网络不好就会一直堵住,导致发送
              数据包接口,一直无法返回。在哪里去设置超时时间?
              */
                if (h->rw_timeout) {
                    if (!wait_since)
                        wait_since = av_gettime_relative();
                    else if (av_gettime_relative() > wait_since + h->rw_timeout)
                        return AVERROR(EIO);
                }
                av_usleep(1000);
            }
        } else if (ret < 1)
            return (ret < 0 && ret != AVERROR_EOF) ? ret : len;
        if (ret) {
            fast_retries = FFMAX(fast_retries, 2);
            wait_since = 0;
        }
        len += ret;
    }
    return len;
}

防止一直阻塞堵住发送数据包接口,使用超时时间,需要在各类协议初始化这里设置超时时间。

static int url_alloc_for_protocol(URLContext **puc, const URLProtocol *up,
                                  const char *filename, int flags,
                                  const AVIOInterruptCB *int_cb)
{
    URLContext *uc;
    int err;

#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK && !ff_network_init())
        return AVERROR(EIO);
#endif
    if ((flags & AVIO_FLAG_READ) && !up->url_read) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for reading\n", up->name);
        return AVERROR(EIO);
    }
    if ((flags & AVIO_FLAG_WRITE) && !up->url_write) {
        av_log(NULL, AV_LOG_ERROR,
               "Impossible to open the '%s' protocol for writing\n", up->name);
        return AVERROR(EIO);
    }
    uc = av_mallocz(sizeof(URLContext) + strlen(filename) + 1);
    if (!uc) {
        err = AVERROR(ENOMEM);
        goto fail;
    }
    uc->av_class = &ffurl_context_class;
    uc->filename = (char *)&uc[1];
    strcpy(uc->filename, filename);
    uc->prot            = up;
    uc->flags           = flags;
    uc->is_streamed     = 0; /* default = not streamed */
    uc->max_packet_size = 0; /* default: stream file */
  /*这里就是设置超时时间,防止卡死的办法*/
	uc->rw_timeout = 3000;//3s
    if (up->priv_data_size) {
        uc->priv_data = av_mallocz(up->priv_data_size);
        if (!uc->priv_data) {
            err = AVERROR(ENOMEM);
            goto fail;
        }
        if (up->priv_data_class) {
            int proto_len= strlen(up->name);
            char *start = strchr(uc->filename, ',');
            *(const AVClass **)uc->priv_data = up->priv_data_class;
            av_opt_set_defaults(uc->priv_data);
            if(!strncmp(up->name, uc->filename, proto_len) && uc->filename + proto_len == start){
                int ret= 0;
                char *p= start;
                char sep= *++p;
                char *key, *val;
                p++;

                if (strcmp(up->name, "subfile"))
                    ret = AVERROR(EINVAL);

                while(ret >= 0 && (key= strchr(p, sep)) && p<key && (val = strchr(key+1, sep))){
                    *val= *key= 0;
                    if (strcmp(p, "start") && strcmp(p, "end")) {
                        ret = AVERROR_OPTION_NOT_FOUND;
                    } else
                        ret= av_opt_set(uc->priv_data, p, key+1, 0);
                    if (ret == AVERROR_OPTION_NOT_FOUND)
                        av_log(uc, AV_LOG_ERROR, "Key '%s' not found.\n", p);
                    *val= *key= sep;
                    p= val+1;
                }
                if(ret<0 || p!=key){
                    av_log(uc, AV_LOG_ERROR, "Error parsing options string %s\n", start);
                    av_freep(&uc->priv_data);
                    av_freep(&uc);
                    err = AVERROR(EINVAL);
                    goto fail;
                }
                memmove(start, key+1, strlen(key));
            }
        }
    }
    if (int_cb)
        uc->interrupt_callback = *int_cb;

    *puc = uc;
    return 0;
fail:
    *puc = NULL;
    if (uc)
        av_freep(&uc->priv_data);
    av_freep(&uc);
#if CONFIG_NETWORK
    if (up->flags & URL_PROTOCOL_FLAG_NETWORK)
        ff_network_close();
#endif
    return err;
}

这个h->prot->url_write是怎样注册呢?实际就是在里注册,并调用rtmp_write函数。

const URLProtocol ff_##flavor##_protocol = {     \
    .name           = #flavor,                   \
    .url_open2      = rtmp_open,                 \
    .url_read       = rtmp_read,                 \
    .url_read_seek  = rtmp_seek,                 \
    .url_read_pause = rtmp_pause,                \
    .url_write      = rtmp_write,                \
    .url_close      = rtmp_close,                \
    .priv_data_size = sizeof(RTMPContext),       \
    .flags          = URL_PROTOCOL_FLAG_NETWORK, \
    .priv_data_class= &flavor##_class,           \
};
static int rtmp_write(URLContext *s, const uint8_t *buf, int size)
{
    RTMPContext *rt = s->priv_data;
    int size_temp = size;
    int pktsize, pkttype, copy;
    uint32_t ts;
    const uint8_t *buf_temp = buf;
    uint8_t c;
    int ret;

    do {
        if (rt->skip_bytes) {
            int skip = FFMIN(rt->skip_bytes, size_temp);
            buf_temp       += skip;
            size_temp      -= skip;
            rt->skip_bytes -= skip;
            continue;
        }
			/*拼接RTMP头*/
        if (rt->flv_header_bytes < RTMP_HEADER) {
            const uint8_t *header = rt->flv_header;
            int channel = RTMP_AUDIO_CHANNEL;

            copy = FFMIN(RTMP_HEADER - rt->flv_header_bytes, size_temp);
            bytestream_get_buffer(&buf_temp, rt->flv_header + rt->flv_header_bytes, copy);
            rt->flv_header_bytes += copy;
            size_temp            -= copy;
            if (rt->flv_header_bytes < RTMP_HEADER)
                break;

            pkttype = bytestream_get_byte(&header);
            pktsize = bytestream_get_be24(&header);
            ts = bytestream_get_be24(&header);
            ts |= bytestream_get_byte(&header) << 24;
            bytestream_get_be24(&header);
            rt->flv_size = pktsize;

            if (pkttype == RTMP_PT_VIDEO)
                channel = RTMP_VIDEO_CHANNEL;

            if (((pkttype == RTMP_PT_VIDEO || pkttype == RTMP_PT_AUDIO) && ts == 0) ||
                pkttype == RTMP_PT_NOTIFY) {
                if ((ret = ff_rtmp_check_alloc_array(&rt->prev_pkt[1],
                                                     &rt->nb_prev_pkt[1],
                                                     channel)) < 0)
                    return ret;
               /*强制发送12字节*/
                rt->prev_pkt[1][channel].channel_id = 0;
            }

          ............
					/*Rtmp发送数据*/
            if ((ret = rtmp_send_packet(rt, &rt->out_pkt, 0)) < 0)
                return ret;
            rt->flv_size = 0;
            rt->flv_off = 0;
            rt->flv_header_bytes = 0;
            rt->flv_nb_packets++;
        }
    } while (buf_temp - buf < size);

    if (rt->flv_nb_packets < rt->flush_interval)
        return size;
    rt->flv_nb_packets = 0;

    /* set stream into nonblocking mode */
    rt->stream->flags |= AVIO_FLAG_NONBLOCK;

    /* try to read one byte from the stream */
    ret = ffurl_read(rt->stream, &c, 1);

    /* switch the stream back into blocking mode */
    rt->stream->flags &= ~AVIO_FLAG_NONBLOCK;

    if (ret == AVERROR(EAGAIN)) {
        /* no incoming data to handle */
        return size;
    } else if (ret < 0) {
        return ret;
    } else if (ret == 1) {
        RTMPPacket rpkt = { 0 };

        if ((ret = ff_rtmp_packet_read_internal(rt->stream, &rpkt,
                                                rt->in_chunk_size,
                                                &rt->prev_pkt[0],
                                                &rt->nb_prev_pkt[0], c)) <= 0)
             return ret;

        if ((ret = rtmp_parse_result(s, rt, &rpkt)) < 0)
            return ret;

        ff_rtmp_packet_destroy(&rpkt);
    }

    return size;
}

int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt,
                         int chunk_size, RTMPPacket **prev_pkt_ptr,
                         int *nb_prev_pkt)
{
    uint8_t pkt_hdr[16], *p = pkt_hdr;
    int mode = RTMP_PS_TWELVEBYTES;
    int off = 0;
    int written = 0;
    int ret;
    RTMPPacket *prev_pkt;
    int use_delta; // flag if using timestamp delta, not RTMP_PS_TWELVEBYTES
    uint32_t timestamp; // full 32-bit timestamp or delta value

    if ((ret = ff_rtmp_check_alloc_array(prev_pkt_ptr, nb_prev_pkt,
                                         pkt->channel_id)) < 0)
        return ret;
    prev_pkt = *prev_pkt_ptr;

    //if channel_id = 0, this is first presentation of prev_pkt, send full hdr.
    use_delta = prev_pkt[pkt->channel_id].channel_id &&
        pkt->extra == prev_pkt[pkt->channel_id].extra &&
        pkt->timestamp >= prev_pkt[pkt->channel_id].timestamp;

    timestamp = pkt->timestamp;
    if (use_delta) {
        timestamp -= prev_pkt[pkt->channel_id].timestamp;
    }
    if (timestamp >= 0xFFFFFF) {
        pkt->ts_field = 0xFFFFFF;
    } else {
        pkt->ts_field = timestamp;
    }

    if (use_delta) {
        if (pkt->type == prev_pkt[pkt->channel_id].type &&
            pkt->size == prev_pkt[pkt->channel_id].size) {
            mode = RTMP_PS_FOURBYTES;
            if (pkt->ts_field == prev_pkt[pkt->channel_id].ts_field)
                mode = RTMP_PS_ONEBYTE;
        } else {
            mode = RTMP_PS_EIGHTBYTES;
        }
    }

    if (pkt->channel_id < 64) {
        bytestream_put_byte(&p, pkt->channel_id | (mode << 6));
    } else if (pkt->channel_id < 64 + 256) {
        bytestream_put_byte(&p, 0               | (mode << 6));
        bytestream_put_byte(&p, pkt->channel_id - 64);
    } else {
        bytestream_put_byte(&p, 1               | (mode << 6));
        bytestream_put_le16(&p, pkt->channel_id - 64);
    }
    if (mode != RTMP_PS_ONEBYTE) {
        bytestream_put_be24(&p, pkt->ts_field);
        if (mode != RTMP_PS_FOURBYTES) {
            bytestream_put_be24(&p, pkt->size);
            bytestream_put_byte(&p, pkt->type);
            if (mode == RTMP_PS_TWELVEBYTES)
                bytestream_put_le32(&p, pkt->extra);
        }
    }
    if (pkt->ts_field == 0xFFFFFF)
        bytestream_put_be32(&p, timestamp);
    // save history
    prev_pkt[pkt->channel_id].channel_id = pkt->channel_id;
    prev_pkt[pkt->channel_id].type       = pkt->type;
    prev_pkt[pkt->channel_id].size       = pkt->size;
    prev_pkt[pkt->channel_id].timestamp  = pkt->timestamp;
    prev_pkt[pkt->channel_id].ts_field   = pkt->ts_field;
    prev_pkt[pkt->channel_id].extra      = pkt->extra;

    if ((ret = ffurl_write(h, pkt_hdr, p - pkt_hdr)) < 0)
        return ret;
    written = p - pkt_hdr + pkt->size;
    while (off < pkt->size) {
        int towrite = FFMIN(chunk_size, pkt->size - off);
      /*这里又回到之前AVIO.c的发送,就是如此往复的去发送数据*/
        if ((ret = ffurl_write(h, pkt->data + off, towrite)) < 0)
            return ret;
        off += towrite;
        if (off < pkt->size) {
            uint8_t marker = 0xC0 | pkt->channel_id;
            if ((ret = ffurl_write(h, &marker, 1)) < 0)
                return ret;
            written++;
            if (pkt->ts_field == 0xFFFFFF) {
                uint8_t ts_header[4];
                AV_WB32(ts_header, timestamp);
                if ((ret = ffurl_write(h, ts_header, 4)) < 0)
                    return ret;
                written += 4;
            }
        }
    }
    return written;
}

由于Rtmp协议也是基于Tcp协议,所以在rtmp_open这个函数里,就已经使用Tcp去连接服务器。实际上最终的收发数据,都是通过Tcp。

static int rtmp_open(URLContext *s, const char *uri, int flags, AVDictionary **opts)
{
    RTMPContext *rt = s->priv_data;
    char proto[8], hostname[256], path[1024], auth[100], *fname;
    char *old_app, *qmark, *n, fname_buffer[1024];
    uint8_t buf[2048];
    int port;
    int ret;

    if (rt->listen_timeout > 0)
        rt->listen = 1;

    rt->is_input = !(flags & AVIO_FLAG_WRITE);

    av_url_split(proto, sizeof(proto), auth, sizeof(auth),
                 hostname, sizeof(hostname), &port,
                 path, sizeof(path), s->filename);

    n = strchr(path, ' ');
    if (n) {
        av_log(s, AV_LOG_WARNING,
               "Detected librtmp style URL parameters, these aren't supported "
               "by the libavformat internal RTMP handler currently enabled. "
               "See the documentation for the correct way to pass parameters.\n");
        *n = '\0'; // Trim not supported part
    }

    if (auth[0]) {
        char *ptr = strchr(auth, ':');
        if (ptr) {
            *ptr = '\0';
            av_strlcpy(rt->username, auth, sizeof(rt->username));
            av_strlcpy(rt->password, ptr + 1, sizeof(rt->password));
        }
    }

    if (rt->listen && strcmp(proto, "rtmp")) {
        av_log(s, AV_LOG_ERROR, "rtmp_listen not available for %s\n",
               proto);
        return AVERROR(EINVAL);
    }
    if (!strcmp(proto, "rtmpt") || !strcmp(proto, "rtmpts")) {
        if (!strcmp(proto, "rtmpts"))
            av_dict_set(opts, "ffrtmphttp_tls", "1", 1);

        /* open the http tunneling connection */
        ff_url_join(buf, sizeof(buf), "ffrtmphttp", NULL, hostname, port, NULL);
    } else if (!strcmp(proto, "rtmps")) {
        /* open the tls connection */
        if (port < 0)
            port = RTMPS_DEFAULT_PORT;
        ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL);
    } else if (!strcmp(proto, "rtmpe") || (!strcmp(proto, "rtmpte"))) {
        if (!strcmp(proto, "rtmpte"))
            av_dict_set(opts, "ffrtmpcrypt_tunneling", "1", 1);

        /* open the encrypted connection */
        ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
        rt->encrypted = 1;
    } else {
        /* 打开Tcp连接 */
        if (port < 0)
            port = RTMP_DEFAULT_PORT;
        if (rt->listen)
          //一直等待这个连接线程结束
            ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port,
                        "?listen&listen_timeout=%d",
                        rt->listen_timeout * 1000);
        else
            ff_url_join(buf, sizeof(buf), "tcp", NULL, hostname, port, NULL);
    }

reconnect:
    if ((ret = ffurl_open_whitelist(&rt->stream, buf, AVIO_FLAG_READ_WRITE,
                                    &s->interrupt_callback, opts,
                                    s->protocol_whitelist, s->protocol_blacklist, s)) < 0) {
        av_log(s , AV_LOG_ERROR, "Cannot open connection %s\n", buf);
        goto fail;
    }

    if (rt->swfverify) {
        if ((ret = rtmp_calc_swfhash(s)) < 0)
            goto fail;
    }

    rt->state = STATE_START;
    if (!rt->listen && (ret = rtmp_handshake(s, rt)) < 0)
        goto fail;
    if (rt->listen && (ret = rtmp_server_handshake(s, rt)) < 0)
        goto fail;

    rt->out_chunk_size = 128;
    rt->in_chunk_size  = 128; // Probably overwritten later
    rt->state = STATE_HANDSHAKED;

    // Keep the application name when it has been defined by the user.
    old_app = rt->app;

    rt->app = av_malloc(APP_MAX_LENGTH);
    if (!rt->app) {
        ret = AVERROR(ENOMEM);
        goto fail;
    }

    //extract "app" part from path
    qmark = strchr(path, '?');
    if (qmark && strstr(qmark, "slist=")) {
        char* amp;
        // After slist we have the playpath, the full path is used as app
        av_strlcpy(rt->app, path + 1, APP_MAX_LENGTH);
        fname = strstr(path, "slist=") + 6;
        // Strip any further query parameters from fname
        amp = strchr(fname, '&');
        if (amp) {
            av_strlcpy(fname_buffer, fname, FFMIN(amp - fname + 1,
                                                  sizeof(fname_buffer)));
            fname = fname_buffer;
        }
    } else if (!strncmp(path, "/ondemand/", 10)) {
        fname = path + 10;
        memcpy(rt->app, "ondemand", 9);
    } else {
        char *next = *path ? path + 1 : path;
        char *p = strchr(next, '/');
        if (!p) {
            if (old_app) {
                // If name of application has been defined by the user, assume that
                // playpath is provided in the URL
                fname = next;
            } else {
                fname = NULL;
                av_strlcpy(rt->app, next, APP_MAX_LENGTH);
            }
        } else {
            // make sure we do not mismatch a playpath for an application instance
            char *c = strchr(p + 1, ':');
            fname = strchr(p + 1, '/');
            if (!fname || (c && c < fname)) {
                fname = p + 1;
                av_strlcpy(rt->app, path + 1, FFMIN(p - path, APP_MAX_LENGTH));
            } else {
                fname++;
                av_strlcpy(rt->app, path + 1, FFMIN(fname - path - 1, APP_MAX_LENGTH));
            }
        }
    }

    if (old_app) {
        // The name of application has been defined by the user, override it.
        if (strlen(old_app) >= APP_MAX_LENGTH) {
            ret = AVERROR(EINVAL);
            goto fail;
        }
        av_free(rt->app);
        rt->app = old_app;
    }

    if (!rt->playpath) {
        rt->playpath = av_malloc(PLAYPATH_MAX_LENGTH);
        if (!rt->playpath) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }

        if (fname) {
            int len = strlen(fname);
            if (!strchr(fname, ':') && len >= 4 &&
                (!strcmp(fname + len - 4, ".f4v") ||
                 !strcmp(fname + len - 4, ".mp4"))) {
                memcpy(rt->playpath, "mp4:", 5);
            } else {
                if (len >= 4 && !strcmp(fname + len - 4, ".flv"))
                    fname[len - 4] = '\0';
                rt->playpath[0] = 0;
            }
            av_strlcat(rt->playpath, fname, PLAYPATH_MAX_LENGTH);
        } else {
            rt->playpath[0] = '\0';
        }
    }

    if (!rt->tcurl) {
        rt->tcurl = av_malloc(TCURL_MAX_LENGTH);
        if (!rt->tcurl) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        ff_url_join(rt->tcurl, TCURL_MAX_LENGTH, proto, NULL, hostname,
                    port, "/%s", rt->app);
    }

    if (!rt->flashver) {
        rt->flashver = av_malloc(FLASHVER_MAX_LENGTH);
        if (!rt->flashver) {
            ret = AVERROR(ENOMEM);
            goto fail;
        }
        if (rt->is_input) {
            snprintf(rt->flashver, FLASHVER_MAX_LENGTH, "%s %d,%d,%d,%d",
                    RTMP_CLIENT_PLATFORM, RTMP_CLIENT_VER1, RTMP_CLIENT_VER2,
                    RTMP_CLIENT_VER3, RTMP_CLIENT_VER4);
        } else {
            snprintf(rt->flashver, FLASHVER_MAX_LENGTH,
                    "FMLE/3.0 (compatible; %s)", LIBAVFORMAT_IDENT);
        }
    }

    rt->receive_report_size = 1048576;
    rt->bytes_read = 0;
    rt->has_audio = 0;
    rt->has_video = 0;
    rt->received_metadata = 0;
    rt->last_bytes_read = 0;
    rt->max_sent_unacked = 2500000;
    rt->duration = 0;

    av_log(s, AV_LOG_DEBUG, "Proto = %s, path = %s, app = %s, fname = %s\n",
           proto, path, rt->app, rt->playpath);
    if (!rt->listen) {
        if ((ret = gen_connect(s, rt)) < 0)
            goto fail;
    } else {
        if ((ret = read_connect(s, s->priv_data)) < 0)
            goto fail;
    }

    do {
        ret = get_packet(s, 1);
    } while (ret == AVERROR(EAGAIN));
    if (ret < 0)
        goto fail;

    if (rt->do_reconnect) {
        int i;
        ffurl_close(rt->stream);
        rt->stream       = NULL;
        rt->do_reconnect = 0;
        rt->nb_invokes   = 0;
        for (i = 0; i < 2; i++)
            memset(rt->prev_pkt[i], 0,
                   sizeof(**rt->prev_pkt) * rt->nb_prev_pkt[i]);
        free_tracked_methods(rt);
        goto reconnect;
    }

    if (rt->is_input) {
        // generate FLV header for demuxer
        rt->flv_size = 13;
        if ((ret = av_reallocp(&rt->flv_data, rt->flv_size)) < 0)
            goto fail;
        rt->flv_off  = 0;
        memcpy(rt->flv_data, "FLV\1\0\0\0\0\011\0\0\0\0", rt->flv_size);

        // Read packets until we reach the first A/V packet or read metadata.
        // If there was a metadata package in front of the A/V packets, we can
        // build the FLV header from this. If we do not receive any metadata,
        // the FLV decoder will allocate the needed streams when their first
        // audio or video packet arrives.
        while (!rt->has_audio && !rt->has_video && !rt->received_metadata) {
            if ((ret = get_packet(s, 0)) < 0)
               goto fail;
        }

        // Either after we have read the metadata or (if there is none) the
        // first packet of an A/V stream, we have a better knowledge about the
        // streams, so set the FLV header accordingly.
        if (rt->has_audio) {
            rt->flv_data[4] |= FLV_HEADER_FLAG_HASAUDIO;
        }
        if (rt->has_video) {
            rt->flv_data[4] |= FLV_HEADER_FLAG_HASVIDEO;
        }

        // If we received the first packet of an A/V stream and no metadata but
        // the server returned a valid duration, create a fake metadata packet
        // to inform the FLV decoder about the duration.
        if (!rt->received_metadata && rt->duration > 0) {
            if ((ret = inject_fake_duration_metadata(rt)) < 0)
                goto fail;
        }
    } else {
        rt->flv_size = 0;
        rt->flv_data = NULL;
        rt->flv_off  = 0;
        rt->skip_bytes = 13;
    }

    s->max_packet_size = rt->stream->max_packet_size;
    s->is_streamed     = 1;
    return 0;

fail:
    av_dict_free(opts);
    rtmp_close(s);
    return ret;
}

我们之前设置了超时时间,解决了网络不好一直阻塞的办法,那我们经过这么久的分析,还没找到在哪里堵塞?那现在就到Tcp.c去找找。

/* return non zero if error */
static int tcp_open(URLContext *h, const char *uri, int flags)
{
    struct addrinfo hints = { 0 }, *ai, *cur_ai;
    int port, fd = -1;
    TCPContext *s = h->priv_data;
    const char *p;
    char buf[256];
    int ret;
    char hostname[1024],proto[1024],path[1024];
    char portstr[10];
  /*默认TCp连接时间*/
    s->open_timeout = 5000000;

    av_url_split(proto, sizeof(proto), NULL, 0, hostname, sizeof(hostname),
        &port, path, sizeof(path), uri);
    if (strcmp(proto, "tcp"))
        return AVERROR(EINVAL);
    if (port <= 0 || port >= 65536) {
        av_log(h, AV_LOG_ERROR, "Port missing in uri\n");
        return AVERROR(EINVAL);
    }
    p = strchr(uri, '?');
    if (p) {
        if (av_find_info_tag(buf, sizeof(buf), "listen", p)) {
            char *endptr = NULL;
            s->listen = strtol(buf, &endptr, 10);
            /* assume if no digits were found it is a request to enable it */
            if (buf == endptr)
                s->listen = 1;
        }
        if (av_find_info_tag(buf, sizeof(buf), "timeout", p)) {
            s->rw_timeout = strtol(buf, NULL, 10);
        }
        if (av_find_info_tag(buf, sizeof(buf), "listen_timeout", p)) {
            s->listen_timeout = strtol(buf, NULL, 10);
        }
    }
    if (s->rw_timeout >= 0) {
     //这里就是把之前的超时时间设置进来了
      s->open_timeout =
        h->rw_timeout   = s->rw_timeout;
    }
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    snprintf(portstr, sizeof(portstr), "%d", port);
    if (s->listen)
        hints.ai_flags |= AI_PASSIVE;
    if (!hostname[0])
        ret = getaddrinfo(NULL, portstr, &hints, &ai);
    else
        ret = getaddrinfo(hostname, portstr, &hints, &ai);
    if (ret) {
        av_log(h, AV_LOG_ERROR,
               "Failed to resolve hostname %s: %s\n",
               hostname, gai_strerror(ret));
        return AVERROR(EIO);
    }

    cur_ai = ai;

 restart:
#if HAVE_STRUCT_SOCKADDR_IN6
    // workaround for IOS9 getaddrinfo in IPv6 only network use hardcode IPv4 address can not resolve port number.
    if (cur_ai->ai_family == AF_INET6){
        struct sockaddr_in6 * sockaddr_v6 = (struct sockaddr_in6 *)cur_ai->ai_addr;
        if (!sockaddr_v6->sin6_port){
            sockaddr_v6->sin6_port = htons(port);
        }
    }
#endif

    fd = ff_socket(cur_ai->ai_family,
                   cur_ai->ai_socktype,
                   cur_ai->ai_protocol);
    if (fd < 0) {
        ret = ff_neterrno();
        goto fail;
    }

    /* Set the socket's send or receive buffer sizes, if specified.
       If unspecified or setting fails, system default is used. */
       //默认收发一次数据是8.5K,频繁收发,网络开销会很大
  //这里最好自己去设置网络发送和接收的缓冲区,否则使用默认的协议栈缓冲区,因为频繁发送数据会导致网络开销大。
    if (s->recv_buffer_size > 0) {
        setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &s->recv_buffer_size, sizeof (s->recv_buffer_size));
    }
    if (s->send_buffer_size > 0) {
        setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &s->send_buffer_size, sizeof (s->send_buffer_size));
    }

    if (s->listen == 2) {
        // multi-client
        if ((ret = ff_listen(fd, cur_ai->ai_addr, cur_ai->ai_addrlen)) < 0)
            goto fail1;
    } else if (s->listen == 1) {
        // single client
        if ((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
                                  s->listen_timeout, h)) < 0)
            goto fail1;
        // Socket descriptor already closed here. Safe to overwrite to client one.
        fd = ret;
    } else {
        if ((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,
                                     s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) {

            if (ret == AVERROR_EXIT)
                goto fail1;
            else
                goto fail;
        }
    }

    h->is_streamed = 1;
    s->fd = fd;

    freeaddrinfo(ai);
    return 0;

 fail:
    if (cur_ai->ai_next) {
        /* Retry with the next sockaddr */
        cur_ai = cur_ai->ai_next;
        if (fd >= 0)
            closesocket(fd);
        ret = 0;
        goto restart;
    }
 fail1:
    if (fd >= 0)
        closesocket(fd);
    freeaddrinfo(ai);
    return ret;
}

tcp发送数据干了什么呢?

static int tcp_write(URLContext *h, const uint8_t *buf, int size)
{
    TCPContext *s = h->priv_data;
    int ret;

    if (!(h->flags & AVIO_FLAG_NONBLOCK)) {
      	//阻塞或超时会调用这个方法
        ret = ff_network_wait_fd_timeout(s->fd, 1, h->rw_timeout, &h->interrupt_callback);
        if (ret)
            return ret;
    }
  //是不是很熟悉啊,这就是Tcp协议的原生接口,不管上面封装怎么变,本质还是没有变
    ret = send(s->fd, buf, size, MSG_NOSIGNAL);
    return ret < 0 ? ff_neterrno() : ret;
}

int ff_network_wait_fd_timeout(int fd, int write, int64_t timeout, AVIOInterruptCB *int_cb)
{
    int ret;
    int64_t wait_start = 0;

    while (1) {
        if (ff_check_interrupt(int_cb))
            return AVERROR_EXIT;
        ret = ff_network_wait_fd(fd, write);
        if (ret != AVERROR(EAGAIN))
            return ret;
      /*如果没有超时时间,这里就不会进来,也就不会返回超时*/
        if (timeout > 0) {
            if (!wait_start)
                wait_start = av_gettime_relative();
            else if (av_gettime_relative() - wait_start > timeout)
                return AVERROR(ETIMEDOUT);
        }
    }
}

int ff_network_wait_fd(int fd, int write)
{
  //输入还是输出呢
    int ev = write ? POLLOUT : POLLIN;
    struct pollfd p = { .fd = fd, .events = ev, .revents = 0 };
    int ret;
  //使用的轮询办法
    ret = poll(&p, 1, POLLING_TIME);
    return ret < 0 ? ff_neterrno() : p.revents & (ev | POLLERR | POLLHUP) ? 0 : AVERROR(EAGAIN);
}

/**多么熟悉的方法*/
int ff_poll(struct pollfd *fds, nfds_t numfds, int timeout)
{
    fd_set read_set;
    fd_set write_set;
    fd_set exception_set;
    nfds_t i;
    int n;
    int rc;

#if HAVE_WINSOCK2_H
    if (numfds >= FD_SETSIZE) {
        errno = EINVAL;
        return -1;
    }
#endif /* HAVE_WINSOCK2_H */

    FD_ZERO(&read_set);
    FD_ZERO(&write_set);
    FD_ZERO(&exception_set);

    n = 0;
    for (i = 0; i < numfds; i++) {
        if (fds[i].fd < 0)
            continue;
#if !HAVE_WINSOCK2_H
        if (fds[i].fd >= FD_SETSIZE) {
            errno = EINVAL;
            return -1;
        }
#endif /* !HAVE_WINSOCK2_H */

        if (fds[i].events & POLLIN)
            FD_SET(fds[i].fd, &read_set);
        if (fds[i].events & POLLOUT)
            FD_SET(fds[i].fd, &write_set);
        if (fds[i].events & POLLERR)
            FD_SET(fds[i].fd, &exception_set);

        if (fds[i].fd >= n)
            n = fds[i].fd + 1;
    }

    if (n == 0)
        /* Hey!? Nothing to poll, in fact!!! */
        return 0;

    if (timeout < 0) {
      /*如果网络不好,这里的写事件就不会触发,也就一直会堵塞在这里,这就是堵塞的本质*/
        rc = select(n, &read_set, &write_set, &exception_set, NULL);
    } else {
        struct timeval tv;
        tv.tv_sec  = timeout / 1000;
        tv.tv_usec = 1000 * (timeout % 1000);
      /*设置了超时时间,就不会堵塞,如果使用epoll,这里也是可以优化,提高效率。特别是针对海量事件*/
        rc         = select(n, &read_set, &write_set, &exception_set, &tv);
    }

    if (rc < 0)
        return rc;

    for (i = 0; i < numfds; i++) {
        fds[i].revents = 0;

        if (FD_ISSET(fds[i].fd, &read_set))
            fds[i].revents |= POLLIN;
        if (FD_ISSET(fds[i].fd, &write_set))
            fds[i].revents |= POLLOUT;
        if (FD_ISSET(fds[i].fd, &exception_set))
            fds[i].revents |= POLLERR;
    }

    return rc;
}
#endif /* !HAVE_POLL_H */

3.总结

经过长篇大论的分析,终于优化了网络阻塞开始的问题,找到了问题的本质。如果你能够看到这里,说明你也能够看懂了。最后总结下,关于发送数据,优化了3处,第一个是增加发送数据大小,第二个增加超时时间,解决阻塞,第三个降低网络负载,提高效率。如果要看懂这里面的关系,需要结合源码,反复多看几次,就会看透本质,实际还是linux最原始的那一套。希望这篇文章能够帮助到各位,谢谢!

原文链接:ffmpeg源码优化之推流发送篇_antonio@qaa的博客-CSDN博客

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值