跳转至

ffmpeg+opencv的简易模板

简易封装和使用

bool is_valid_image(const uint8_t *input, uint64_t len) {
    if (!g_log_callback_set) {
        av_log_set_callback(log_callback);
        g_log_callback_set = true;
    }
    g_ffmpeg_warnings.clear(); // 清空之前的警告信息
    // raii
    static auto free_avformat = [](AVFormatContext **p) {
        if (*p) {
            if ((*p)->pb) {
                av_free((*p)->pb->buffer);
                avio_context_free(&(*p)->pb);
            }
            avformat_free_context(*p);
        }
    };
    static auto free_avcodec = [](AVCodecContext *p) {
        p && (avcodec_free_context(&p), true);
    };
    static auto free_avframe = [](AVFrame *p) {
        p && (av_frame_free(&p), true);
    };
    static auto free_avpacket = [](AVPacket *p) {
        p && (av_packet_free(&p), true);
    };
    static auto free_avio_ctx = [](AVIOContext *p) {
        p && (avio_context_free(&p), true);
    };
    static auto close_stream = [](AVFormatContext *p) {
        p && (avformat_close_input(&p), true);
    };

    auto fmt_ctx = avformat_alloc_context();
    scope_guard guard_fmt_ctx(free_avformat, &fmt_ctx);

    if (fmt_ctx == nullptr)
        return false;
    constexpr uint64_t avio_buf_len = 1024 * 1024ull;
    struct mem_input {
        const uint8_t *start{nullptr};
        const uint8_t *end{nullptr};
        const uint8_t *cur{nullptr};
    };
    static auto read = [](void *opaque, uint8_t *buf, int buf_size) -> int {
        auto &ctx = *reinterpret_cast<mem_input *>(opaque);
        auto residual = ctx.end - ctx.cur;
        auto sz = std::min<uint64_t>(residual, buf_size);
        if (sz > 0) {
            //   memcpy(buf, ctx.cur, sz);
            std::copy(ctx.cur, ctx.cur + sz, buf);
            ctx.cur += sz;
        }
        return sz == 0 ? AVERROR_EOF : sz;
    };
    static auto seek = [](void *opaque, int64_t offset, int whence) -> int64_t {
        auto &ctx = *reinterpret_cast<mem_input *>(opaque);
        auto len = ctx.end - ctx.start;
        int64_t ret = AVERROR_EOF;
        switch (whence) {
        case AVSEEK_SIZE:
            ret = static_cast<int64_t>(ctx.end - ctx.start);
            break;
        case SEEK_SET:
            if (offset >= 0) {
                if (offset >= len) {
                    ret = AVERROR_EOF;
                } else {
                    ctx.cur = ctx.start + offset;
                    ret = 0;
                }
            }
            break;
        }
        return ret;
    };
    mem_input opaque{input, input + len, input};
    fmt_ctx->pb =
        avio_alloc_context(static_cast<uint8_t *>(av_malloc(avio_buf_len)),
                           avio_buf_len, 0, &opaque, read, nullptr, seek);
    //   scope_guard guard_avio_ctx(free_avio_ctx, fmt_ctx->pb);

    // only video
    if (avformat_open_input(&fmt_ctx, nullptr, nullptr, nullptr) < 0 ||
        avformat_find_stream_info(fmt_ctx, nullptr) < 0)
        return false;
    //
    int vid_idx = -1;
    for (int i = 0; i < fmt_ctx->nb_streams; ++i) {
        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            vid_idx = i;
            break;
        }
    }
    if (vid_idx < 0) {
        return false;
    }
    AVCodecContext *codec_ctx = nullptr;
    const auto codecpar = fmt_ctx->streams[vid_idx]->codecpar;
    auto codec = avcodec_find_decoder(codecpar->codec_id);
    if (codec != nullptr) {
        codec_ctx = avcodec_alloc_context3(codec);
        if (codec_ctx == nullptr)
            return false;
        if (avcodec_parameters_to_context(codec_ctx, codecpar) < 0)
            return false;
        if (avcodec_open2(codec_ctx, codec, nullptr) < 0)
            return false;
    }
    scope_guard guard_codec_ctx(free_avcodec, codec_ctx);
    int ret = 0;
    auto pkt = av_packet_alloc();
    scope_guard guard_pkt(free_avpacket, pkt);
    AVFrame *frame = av_frame_alloc();
    scope_guard guard_frame(free_avframe, frame);

    auto handle_packet = [](AVCodecContext *codec_ctx, AVFrame *frame,
                            auto &&fn) -> void {
        int ret = 0;
        while (ret >= 0) {
            ret = avcodec_receive_frame(codec_ctx, frame);
            if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
                av_frame_unref(frame);
                break;
            } else if (ret < 0) {
                av_frame_unref(frame);
                return;
            }
            fn(frame, codec_ctx->frame_number);
            av_frame_unref(frame);
        }
    };

    static auto pgm_save = [](unsigned char *buf, int wrap, int xsize,
                              int ysize, const char *filename) {
        FILE *f;
        int i;

        f = fopen(filename, "wb");
        fprintf(f, "P5\n%d %d\n%d\n", xsize, ysize, 255);
        for (i = 0; i < ysize; i++)
            fwrite(buf + i * wrap, 1, xsize, f);
        fclose(f);
    };

    static auto calc_entropy = [](cv::Mat mat) -> double {
        if (mat.channels() > 1) {
            cv::cvtColor(mat, mat, cv::COLOR_BGR2GRAY);
        }
        assert(mat.channels() == 1);
        cv::Mat grey = mat.clone();
        std::vector<cv::Mat> histChannels;
        cv::Mat hist;
        int histSize = 256;
        float range[] = {0, 256};
        const float *histRange = {range};
        cv::calcHist(&grey, 1, 0, cv::Mat(), hist, 1, &histSize, &histRange);
        hist /= (grey.rows * grey.cols);
        double entropy = 0.0;
        for (int i = 0; i < histSize; i++) {
            float p = hist.at<float>(i);
            if (p > 0) {
                entropy -= p * log(p);
            }
        }
        return entropy;
    };

    static auto calc_edge_ratio = [](cv::Mat mat) {
        assert(mat.channels() == 1);
        cv::Mat gray = mat.clone();
        cv::Canny(gray, gray, 50, 150);
        // cv::imwrite(std::string("D:/zzz/canny.png"), gray);
        return static_cast<double>(cv::countNonZero(gray)) /
               static_cast<double>(gray.rows * gray.cols);
    };

    static auto calc_std_dev = [](cv::Mat mat) -> double {
        assert(mat.channels() == 1);
        cv::Scalar mean, stddev;
        cv::meanStdDev(mat, mean, stddev);
        return stddev[0];
    };

    static auto sobel_edge = [](cv::Mat mat) -> cv::Mat {
        cv::Mat sobel_x, sobel_y;
        cv::Sobel(mat, sobel_x, CV_64F, 1, 0, 3);
        cv::Sobel(mat, sobel_y, CV_64F, 0, 1, 3);
        cv::Mat sobel_xy;
        cv::addWeighted(sobel_x, 0.5, sobel_y, 0.5, 0, sobel_xy);
        return sobel_xy;
    };

    static auto laplacian_edge = [](cv::Mat mat) -> cv::Mat {
        cv::Mat laplacian;
        cv::Laplacian(mat, laplacian, CV_64F, 3);
        return laplacian;
    };

    static auto calc_ratio = [](cv::Mat mat) -> double {
        cv::Mat out;
        cv::threshold(mat, out, 128.0, 255.0, cv::THRESH_BINARY);
        return static_cast<double>(cv::countNonZero(out)) /
               static_cast<double>(out.rows * out.cols);
    };

    static auto is_valid_image = [](cv::Mat mat) -> bool {
        auto entropy = calc_entropy(mat);
        auto edge_ratio = calc_edge_ratio(mat);
        auto std_dev = calc_std_dev(mat);
        return 0.5 <= entropy && entropy <= 7.5 && 0.009 <= edge_ratio &&
               edge_ratio <= 0.15 && 30.0 <= std_dev && std_dev <= 100.0;
    };

    bool flag = false;
    auto img_check_cb = [&](AVFrame *frame, int frame_num) {
        //  std::string filename = std::string("D:/zzz/") +
        //  std::to_string(frame_num) + ".pgm"; pgm_save(frame->data[0],
        //  frame->linesize[0], frame->width, frame->height, filename.c_str());
        // // call opencv and decode
        cv::Mat mat(frame->height, frame->width, CV_8UC1, frame->data[0]);
        if (is_valid_image(mat)) {
            flag = true;
            return;
        }
        // auto sobel_xy = sobel_edge(mat);
        // auto laplacian = laplacian_edge(mat);
        // cv::imwrite(std::string("D:/zzz/sobel-") +
        // std::to_string(frame_num) + ".png", sobel_xy);
        // cv::imwrite(std::string("D:/zzz/laplacian-") +
        // std::to_string(frame_num) + ".png", laplacian); auto sobel_ratio
        // = calc_ratio(sobel_xy); auto laplacian_ratio =
        // calc_ratio(laplacian); std::cout << "sobel: " << sobel_ratio <<
        // "\n"; std::cout << "laplacian: " << laplacian_ratio << "\n";
    };

    while ((ret = av_read_frame(fmt_ctx, pkt)) >= 0) {
        if (pkt->stream_index != vid_idx) {
            av_packet_unref(pkt);
            continue;
        }
        int ret = avcodec_send_packet(codec_ctx, pkt);
        av_packet_unref(pkt);
        if (ret < 0) {
            break;
        }
        if (!g_ffmpeg_warnings.empty()) {
            //
        }
        g_ffmpeg_warnings.clear();

        handle_packet(codec_ctx, frame, img_check_cb);
        av_frame_unref(frame);
    }
    ret = avcodec_send_packet(codec_ctx, nullptr);
    handle_packet(codec_ctx, frame, img_check_cb);
    av_frame_unref(frame);
    if (!g_ffmpeg_warnings.empty()) {
    }
    return flag;
}