FFmpeg实现捕获摄像头并保存成.mp4

环境:Windows10,VS2022,ffmpeg-n8.0-latest-win64-lgpl-shared-8.0

#include <iostream>
#include <string>
extern "C" {
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

using namespace std;

int main() {
    const char* device_name = "video=Integrated Camera";
    const char* output_filename = "output.mp4";

    avdevice_register_all();

    // 1. 打开摄像头输入
    AVFormatContext* input_ctx = nullptr;
    const AVInputFormat* input_format = av_find_input_format("dshow");
    if (!input_format) {
        cerr << "Could not find dshow input format\n";
        return -1;
    }

    if (avformat_open_input(&input_ctx, device_name, input_format, nullptr) < 0) {
        cerr << "Could not open camera device\n";
        return -1;
    }

    if (avformat_find_stream_info(input_ctx, nullptr) < 0) {
        cerr << "Could not find stream info\n";
        avformat_close_input(&input_ctx);
        return -1;
    }

    cout << "Input format opened, found " << input_ctx->nb_streams << " streams.\n";

    // 2. 查找视频流并设置解码器
    int video_stream_index = -1;
    AVCodecContext* decoder_ctx = nullptr;
    
    for (unsigned int i = 0; i < input_ctx->nb_streams; i++) {
        if (input_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream_index = i;
            break;
        }
    }
    
    if (video_stream_index == -1) {
        cerr << "Could not find video stream\n";
        avformat_close_input(&input_ctx);
        return -1;
    }

    AVStream* video_stream = input_ctx->streams[video_stream_index];
    AVCodecParameters* codecpar = video_stream->codecpar;

    // 创建解码器上下文
    const AVCodec* decoder = avcodec_find_decoder(codecpar->codec_id);
    if (!decoder) {
        cerr << "Decoder not found\n";
        avformat_close_input(&input_ctx);
        return -1;
    }

    decoder_ctx = avcodec_alloc_context3(decoder);
    if (!decoder_ctx) {
        cerr << "Could not allocate decoder context\n";
        avformat_close_input(&input_ctx);
        return -1;
    }

    if (avcodec_parameters_to_context(decoder_ctx, codecpar) < 0) {
        cerr << "Could not copy codec parameters to decoder context\n";
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    if (avcodec_open2(decoder_ctx, decoder, nullptr) < 0) {
        cerr << "Could not open decoder\n";
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    // 3. 查找H.264编码器
    const AVCodec* encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!encoder) {
        cerr << "H.264 encoder not found\n";
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    AVCodecContext* encoder_ctx = avcodec_alloc_context3(encoder);
    if (!encoder_ctx) {
        cerr << "Could not allocate encoder context\n";
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    // 编码器设置
    encoder_ctx->codec_id = AV_CODEC_ID_H264;
    encoder_ctx->bit_rate = 4000000;
    encoder_ctx->width = decoder_ctx->width;
    encoder_ctx->height = decoder_ctx->height;
    encoder_ctx->time_base = av_inv_q(av_make_q(30, 1)); // 30 FPS
    encoder_ctx->framerate = av_make_q(30, 1);
    encoder_ctx->gop_size = 12;
    encoder_ctx->max_b_frames = 2;
    encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;

    // H.264特定设置
    av_opt_set(encoder_ctx->priv_data, "preset", "medium", 0);
    av_opt_set(encoder_ctx->priv_data, "tune", "zerolatency", 0);

    if (avcodec_open2(encoder_ctx, encoder, nullptr) < 0) {
        cerr << "Could not open encoder\n";
        avcodec_free_context(&encoder_ctx);
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    // 4. 创建输出文件上下文
    AVFormatContext* output_ctx = nullptr;
    if (avformat_alloc_output_context2(&output_ctx, nullptr, nullptr, output_filename) < 0) {
        cerr << "Could not create output context\n";
        avcodec_free_context(&encoder_ctx);
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    // 添加视频流
    AVStream* out_stream = avformat_new_stream(output_ctx, nullptr);
    if (!out_stream) {
        cerr << "Could not create output stream\n";
        avformat_free_context(output_ctx);
        avcodec_free_context(&encoder_ctx);
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    if (avcodec_parameters_from_context(out_stream->codecpar, encoder_ctx) < 0) {
        cerr << "Could not copy encoder parameters to output stream\n";
        avformat_free_context(output_ctx);
        avcodec_free_context(&encoder_ctx);
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    out_stream->time_base = encoder_ctx->time_base;

    // 打开输出文件
    if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&output_ctx->pb, output_filename, AVIO_FLAG_WRITE) < 0) {
            cerr << "Could not open output file\n";
            avformat_free_context(output_ctx);
            avcodec_free_context(&encoder_ctx);
            avcodec_free_context(&decoder_ctx);
            avformat_close_input(&input_ctx);
            return -1;
        }
    }

    if (avformat_write_header(output_ctx, nullptr) < 0) {
        cerr << "Error occurred when writing header\n";
        if (!(output_ctx->oformat->flags & AVFMT_NOFILE))
            avio_closep(&output_ctx->pb);
        avformat_free_context(output_ctx);
        avcodec_free_context(&encoder_ctx);
        avcodec_free_context(&decoder_ctx);
        avformat_close_input(&input_ctx);
        return -1;
    }

    // 5. 创建格式转换上下文
    struct SwsContext* sws_ctx = nullptr;
    if (decoder_ctx->pix_fmt != AV_PIX_FMT_YUV420P) {
        sws_ctx = sws_getContext(
            decoder_ctx->width, decoder_ctx->height, decoder_ctx->pix_fmt,
            encoder_ctx->width, encoder_ctx->height, AV_PIX_FMT_YUV420P,
            SWS_BICUBIC, nullptr, nullptr, nullptr
        );
        if (!sws_ctx) {
            cerr << "Could not initialize the conversion context\n";
            // 清理资源...
            return -1;
        }
    }

    // 6. 分配帧和包
    AVFrame* input_frame = av_frame_alloc();
    AVFrame* output_frame = av_frame_alloc();
    AVPacket* input_pkt = av_packet_alloc();
    AVPacket* output_pkt = av_packet_alloc();

    if (!input_frame || !output_frame || !input_pkt || !output_pkt) {
        cerr << "Could not allocate frame or packet\n";
        return -1;
    }

    // 为输出帧分配缓冲区
    output_frame->format = AV_PIX_FMT_YUV420P;
    output_frame->width = encoder_ctx->width;
    output_frame->height = encoder_ctx->height;
    if (av_frame_get_buffer(output_frame, 0) < 0) {
        cerr << "Could not allocate frame buffer\n";
        return -1;
    }

    cout << "Start recording... Press Ctrl+C to stop.\n";

    // 7. 主循环:读取、解码、转换、编码、写入
    int64_t frame_pts = 0;
    for (int frame_count = 0; frame_count < 300; frame_count++) {
        if (av_read_frame(input_ctx, input_pkt) < 0) {
            cerr << "Error reading frame\n";
            break;
        }

        if (input_pkt->stream_index == video_stream_index) {
            // 解码
            if (avcodec_send_packet(decoder_ctx, input_pkt) >= 0) {
                while (avcodec_receive_frame(decoder_ctx, input_frame) >= 0) {
                    // 格式转换(如果需要)
                    if (sws_ctx) {
                        sws_scale(sws_ctx, 
                                (const uint8_t * const*)input_frame->data, 
                                input_frame->linesize,
                                0, decoder_ctx->height,
                                output_frame->data, output_frame->linesize);
                    } else {
                        // 如果格式相同,直接复制
                        av_frame_copy(output_frame, input_frame);
                    }

                    // 设置时间戳
                    output_frame->pts = frame_pts++;

                    // 编码
                    if (avcodec_send_frame(encoder_ctx, output_frame) >= 0) {
                        while (avcodec_receive_packet(encoder_ctx, output_pkt) >= 0) {
                            // 调整时间戳
                            av_packet_rescale_ts(output_pkt, encoder_ctx->time_base, out_stream->time_base);
                            output_pkt->stream_index = out_stream->index;

                            // 写入文件
                            if (av_interleaved_write_frame(output_ctx, output_pkt) < 0) {
                                cerr << "Error writing frame\n";
                            }
                            
                            av_packet_unref(output_pkt);
                        }
                    }
                }
            }
        }

        av_packet_unref(input_pkt);
    }

    // 8. 刷新编码器
    avcodec_send_frame(encoder_ctx, nullptr);
    while (avcodec_receive_packet(encoder_ctx, output_pkt) >= 0) {
        av_packet_rescale_ts(output_pkt, encoder_ctx->time_base, out_stream->time_base);
        output_pkt->stream_index = out_stream->index;
        av_interleaved_write_frame(output_ctx, output_pkt);
        av_packet_unref(output_pkt);
    }

    // 9. 写文件尾
    av_write_trailer(output_ctx);

    // 10. 清理资源
    if (sws_ctx)
        sws_freeContext(sws_ctx);
    
    av_frame_free(&input_frame);
    av_frame_free(&output_frame);
    av_packet_free(&input_pkt);
    av_packet_free(&output_pkt);
    
    avcodec_free_context(&decoder_ctx);
    avcodec_free_context(&encoder_ctx);
    avformat_close_input(&input_ctx);

    if (!(output_ctx->oformat->flags & AVFMT_NOFILE)) {
        avio_closep(&output_ctx->pb);
    }
    avformat_free_context(output_ctx);

    cout << "Recording finished, saved to " << output_filename << "\n";
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值