EasyPlayerPro(Windows)開發系列之採用ffmpeg進行錄像

這篇和ffmpeg進行截圖類似,不過省略掉編碼的過程,從網絡上或者文件讀取的數據爲編碼後的數據,直接進行寫文件即可,本文以寫MP4文件爲例進行講解。

1.創建線程執行開啓錄像

    player->record_duration = duration*60;
    player->record_piece_id = 0;
    player->record_time = 0.0f;
    memset(player->record_path, 0, sizeof(MAX_PATH_LENGTH));
    strcpy(player->record_path, file);
    player->bRecording = true;

    //開啓錄像線程
    player->record_thread = CreateThread(NULL, 0, av_record_thread_proc, player, 0, NULL);

2.初始化拉去流進行錄像

void* av_record_thread_proc(void *thread_param)
{
    PLAYER* play = (PLAYER*)thread_param;
    if (!play)
    {
        return NULL;
    }

    AVFormatContext *i_fmt_ctx = NULL;
    AVStream *i_video_stream = NULL;
    AVFormatContext *o_fmt_ctx = NULL;
    AVStream *o_video_stream = NULL;

    av_register_all();
    avcodec_register_all();
    avformat_network_init();

    /* should set to NULL so that avformat_open_input() allocate a new one */
    i_fmt_ctx = NULL;

    if (avformat_open_input(&i_fmt_ctx, play->file_url, NULL, NULL) != 0)
    {
        //fprintf(stderr, "could not open input file\n");
        return NULL;
    }

    int nRet = avformat_find_stream_info(i_fmt_ctx, NULL);
    if (nRet<0)
    {
        ///fprintf(stderr, "could not find stream info\n");
        return NULL;
    }

    //bool bSupportVideo = true;
    //bool bSupportAudio = true;
    int nVideoIndex = -1;
    int nAudioIndex = -1;

    int nPathLen = strlen(play->record_path);
    char *csFileName = new char[nPathLen];
    memset(csFileName, 0, nPathLen);
    strncpy(csFileName, play->record_path, nPathLen-4);

    char sSliceupName[MAX_PATH_LENGTH] = {0,};

    if (play->record_duration > 0)
    {
        sprintf(sSliceupName, "%s_%d.mp4", csFileName, play->record_piece_id);
    }
    else
    {
        sprintf(sSliceupName, "%s.mp4", csFileName);
    }
    //創建MP4文件
    if (CreateMediaFile(&o_fmt_ctx, i_fmt_ctx, sSliceupName) == 0)
    {
        return 0;
    }

    /*
    * since all input files are supposed to be identical (framerate, dimension, color format, ...)
    * we can safely set output codec values from first input file
    */
    int nOutStreamId = 0;
    for (int i = 0; i < i_fmt_ctx->nb_streams; i++)
    {
        AVStream *in_stream = i_fmt_ctx->streams[i];
        if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            AVCodecID codecId = in_stream->codec->codec_id;
            //音頻格式過濾(just support aac,mp3)
            if ((codecId != AV_CODEC_ID_MP3 && codecId != AV_CODEC_ID_AAC )/*|| in_stream->codec->extradata_size==0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
            {
                //bSupportAudio = false;
                continue;
            }
            nAudioIndex = nOutStreamId;

        }
        if (i_fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) 
        {
            AVCodecID codecId = in_stream->codec->codec_id;
            //視頻頻格式過濾(just support h264,265)
            if ((codecId != AV_CODEC_ID_H264 && codecId != AV_CODEC_ID_H265) /*|| in_stream->codec->extradata_size == 0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
            {
                //bSupportVideo = false;
                continue;
            }
            nVideoIndex = nOutStreamId;
        }
        nOutStreamId++;
    }

    int start_pts = -1;
    int start_dts = -1;
    int64_t audio_start_pts = -1;
    int64_t audio_start_dts = -1;
    int64_t video_start_pts = -1;
    int64_t video_start_dts = -1;

    bool audio_re_record = false;
    bool video_re_record = false;

    int64_t pts, dts;
    //int total_frame = 3000;//寫3000幀文件  
    //while (total_frame--)
    while (play->bRecording)
    {
        AVPacket i_pkt;
        av_init_packet(&i_pkt);
        i_pkt.size = 0;
        i_pkt.data = NULL;

        if (av_read_frame(i_fmt_ctx, &i_pkt) <0)
            break;

        //ret = av_interleaved_write_frame(pOFormat, tmppkt);
        AVStream *in_stream = i_fmt_ctx->streams[i_pkt.stream_index];
        AVStream *out_stream = NULL;

        if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO && nVideoIndex > -1 )
        {
            int nTimeBase = in_stream->time_base.den;
            if (nTimeBase>0)
                play->record_time = (float)(i_pkt.dts - start_dts) / nTimeBase;

            //TRACE("Video Timestamp:   %f time_base = %d %lld %lld duration = %d  \n", m_fRecordTime, in_stream->time_base.den, i_pkt.pts, i_pkt.dts, i_pkt.duration);
            if (start_pts < 0)
                start_pts = i_pkt.pts;
            if (start_dts < 0)
                start_dts = i_pkt.dts;

            //錄像時間,單位: S
            float fRecTime = 0.0f;
            if (nTimeBase>0)
                fRecTime = (float)(i_pkt.dts - start_dts) / nTimeBase;
            //判斷是否達到切片的要求
            if (play->record_duration > 0 && fRecTime > play->record_duration && i_pkt.flags == AV_PKT_FLAG_KEY)
            {
                play->record_piece_id++;
                //關閉已經完成切片的文件
                CloseMediaFile(o_fmt_ctx);
                memset(sSliceupName, 0x00, MAX_PATH_LENGTH);
                sprintf(sSliceupName, "%s_%d.mp4", csFileName, play->record_piece_id);

                //創建MP4文件
                if (CreateMediaFile(&o_fmt_ctx, i_fmt_ctx, sSliceupName) == 0)
                {
                    return 0;
                }
                start_pts = i_pkt.pts;
                start_dts = i_pkt.dts;
                audio_re_record = true;
                video_re_record = true;

            }
        }
        else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO && nAudioIndex > -1 && nVideoIndex == -1)
        {
            int nTimeBase = in_stream->time_base.den;
            if (nTimeBase>0)
                play->record_time = (float)(i_pkt.dts) / nTimeBase;

            //TRACE("Audio Timestamp:   %f time_base = %d %lld %lld duration = %d  \n", play->record_time, in_stream->time_base.den, i_pkt.pts, i_pkt.dts, i_pkt.duration);
            if (start_pts < 0)
                start_pts = i_pkt.pts;
            if (start_dts < 0)
                start_dts = i_pkt.dts;

            //錄像時間,單位: S
            float fRecTime = 0.0f;
            if (nTimeBase>0)
                fRecTime = (float)(i_pkt.dts - start_dts) / nTimeBase;
            //判斷是否達到切片的要求
            if (play->record_duration > 0 && fRecTime > play->record_duration)
            {
                play->record_piece_id++;
                //關閉已經完成切片的文件
                CloseMediaFile(o_fmt_ctx);
                memset(sSliceupName, 0x00, 512);
                sprintf(sSliceupName, "%s_%d.mp4", csFileName, play->record_piece_id);

                //創建MP4文件
                if (CreateMediaFile(&o_fmt_ctx, i_fmt_ctx, sSliceupName) == 0)
                {
                    return 0;
                }
                start_pts = i_pkt.pts;
                start_dts = i_pkt.dts;
                audio_re_record = true;
                video_re_record = true;
            }
        }

        if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)//不支持的視頻 過濾
        {
            if( nVideoIndex == -1)
                continue;
            out_stream  = o_fmt_ctx->streams[nVideoIndex];

            if (video_start_pts < 0)
                video_start_pts = i_pkt.pts;
            if (video_start_dts < 0)
                video_start_dts = i_pkt.dts;

            if (video_re_record)
            {
                video_start_pts = i_pkt.pts;
                video_start_dts = i_pkt.dts;
                video_re_record = false;
            }
            i_pkt.pts = i_pkt.pts - video_start_pts;
            i_pkt.dts = i_pkt.dts - video_start_dts;
        }

        if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO )//不支持的音頻 過濾
        {
            if (nAudioIndex == -1)
                continue;
            out_stream = o_fmt_ctx->streams[nAudioIndex];

            if (audio_start_pts < 0)
                audio_start_pts = i_pkt.pts;
            if (audio_start_dts < 0)
                audio_start_dts = i_pkt.dts;

            if (audio_re_record)
            {
                audio_start_pts = i_pkt.pts;
                audio_start_dts = i_pkt.dts;
                audio_re_record = false;
            }
            i_pkt.pts = i_pkt.pts - audio_start_pts;
            i_pkt.dts = i_pkt.dts - audio_start_dts;

        }
        if (!out_stream)
            continue;

        i_pkt.pts = (i_pkt.pts > 0) ? i_pkt.pts : 0;
        i_pkt.dts = (i_pkt.dts > 0) ? i_pkt.dts : 0;
        i_pkt.duration = (i_pkt.duration > 0) ? i_pkt.duration : 0;

        i_pkt.pts = av_rescale_q_rnd(i_pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
        i_pkt.dts = av_rescale_q_rnd(i_pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
        i_pkt.duration = av_rescale_q(i_pkt.duration, in_stream->time_base, out_stream->time_base);
        i_pkt.pos = -1;

        int ret = av_interleaved_write_frame(o_fmt_ctx, &i_pkt);
        if (ret < 0)
            continue;
            //break;
    }

    avformat_close_input(&i_fmt_ctx);

    av_write_trailer(o_fmt_ctx);

    avcodec_close(o_fmt_ctx->streams[0]->codec);
    av_freep(&o_fmt_ctx->streams[0]->codec);
    av_freep(&o_fmt_ctx->streams[0]);

    avio_close(o_fmt_ctx->pb);
    av_free(o_fmt_ctx);
    av_bitstream_filter_close(aacBsf);
    if (csFileName)
        delete[] csFileName;
    return NULL;
}

縱觀以上錄像代碼,通過另外開闢線程進行錄像,線程執行過程分爲以下幾個部分:
1.拉流讀取流數據模塊
關於這塊不做過多贅述,大家有興趣可以參考系列文章的前幾篇文章;

2.錄像切片
由於mp4文件過長可能導致播放不了的問題,所以我們支持對錄製mp4文件進行切片錄像,錄像參考時間戳默認以視頻爲準,如果沒有視頻則以音頻爲準,判斷條件以設置的一段MP4長度爲準:

if (play->record_duration > 0 && fRecTime > play->record_duration)

當達到可以切片的條件時,創建文件進行切片,也就是關掉上一次的錄像,重新開啓下一個錄像,創建和關閉錄像如下:

int CreateMediaFile(AVFormatContext ** o_fmt_ctx, AVFormatContext *i_fmt_ctx, char *csFileName)
{
    int RET = avformat_alloc_output_context2(o_fmt_ctx, NULL, NULL, csFileName);
    /*
    * since all input files are supposed to be identical (framerate, dimension, color format, ...)
    * we can safely set output codec values from first input file
    */
    for (int i = 0; i < i_fmt_ctx->nb_streams; i++)
    {
        AVStream *in_stream = i_fmt_ctx->streams[i];
        if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            AVCodecID codecId = in_stream->codec->codec_id;
            //音頻格式過濾(just support aac,mp3)
            if ((codecId != AV_CODEC_ID_MP3 && codecId != AV_CODEC_ID_AAC) /*|| in_stream->codec->extradata_size == 0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
            {
                //bSupportAudio = false;
                continue;
            }
        }
        if (i_fmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            AVCodecID codecId = in_stream->codec->codec_id;
            //視頻頻格式過濾(just support h264,265if ((codecId != AV_CODEC_ID_H264 && codecId != AV_CODEC_ID_H265) /*|| in_stream->codec->extradata_size == 0*/) ///< preferred ID for decoding MPEG audio layer 1, 2 or 3
            {
                //bSupportVideo = false;
                continue;
            }
        }

        //// 獲取AVC結構,包涵SPSPPS [Dingshuai 2017/07/12]
        //for (int j = 0;j<i_fmt_ctx->streams[i]->codec->extradata_size;j++)
        //{
        //  TRACE("%x ", i_fmt_ctx->streams[i]->codec->extradata[j]);
        //}

        AVStream *out_stream = avformat_new_stream(*o_fmt_ctx, in_stream->codec->codec);
        if (!out_stream)
        {
            continue;
            //return 0;
        }

        int ret = 0;
        ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
        if (ret < 0) {
            fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
            return 0;
        }
        out_stream->codec->codec_tag = 0;
        if ((*o_fmt_ctx)->oformat->flags & AVFMT_GLOBALHEADER)
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
    }

    avio_open(&((*o_fmt_ctx)->pb), csFileName, AVIO_FLAG_WRITE);

    av_dump_format((*o_fmt_ctx), 0, csFileName, 1);

    if (avformat_write_header((*o_fmt_ctx), NULL) < 0)
    {
        return 0;
    }
    return 1;
}
void CloseMediaFile(AVFormatContext* o_fmt_ctx)
{
    av_write_trailer(o_fmt_ctx);

    avcodec_close(o_fmt_ctx->streams[0]->codec);
    av_freep(&o_fmt_ctx->streams[0]->codec);
    av_freep(&o_fmt_ctx->streams[0]);

    avio_close(o_fmt_ctx->pb);
    av_free(o_fmt_ctx);
}

需要注意的是,編譯ffmpeg的時候需要將寫文件相關的模塊編譯進去,否則avformat_alloc_output_context2就會調用出錯,還需要注意的就是寫MP4支持的格式有限,上文代碼中限定只支持aac和MP3格式,爲了格式統一,這裏應該將其他不支持的格式均轉成aac或者MP3這種MP4錄製、所支持的格式,大家可以參考上文截圖的做法進行重編碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章