libav как поменять потоковый кодек - PullRequest
0 голосов
/ 09 ноября 2018

Я пытаюсь воспроизвести с помощью libav то же, что и следующая команда ffmpeg:

ffmpeg -f v4l2 -кадр 25 -видео_размер 640x480 -i / dev / video 0 -f mpegts -codec: v mpeg1video -s 640x480 -b: v 1000k -bf 0 -muxdelay 0,001 http://localhost:8081/supersecret

Мне удается воспроизвести большую часть этого. Проблема в том, что при выделении потока «mpegts» (строка 23) выбран кодек «mpeg2video», но мне нужно, чтобы он был «mpeg1video». Я попытался заставить переменную codec_id быть «mpeg1video» (строка 25), и это вроде как сработало, хотя я получаю много артефактов на изображении, так что я предполагаю, что это не так, как вы это делаете. Как правильно изменить кодек в этом случае (т. Е. "- codec: v mpeg1video" часть команды ffmpeg)?

Используемый код C ++:

#include <exception>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/error.h>
#include <libavutil/frame.h>
#include <libavutil/imgutils.h>
}

struct AVStreamer {
  AVFormatContext* format_context;
  AVStream* stream;
  AVCodecContext* codec_context;
  AVCodec* codec;
  AVFrame* frame;
  int64_t next_pts;

  void init_format_context(const char* url) {
    avformat_alloc_output_context2(&format_context, nullptr, "mpegts", url);
    if (format_context == nullptr) throw std::runtime_error("Could not create output context");
    format_context->oformat->video_codec = AV_CODEC_ID_MPEG1VIDEO;
  }

  void init_codec() {
    auto codec_id = format_context->oformat->video_codec;
    codec = avcodec_find_encoder(codec_id);
    if (codec == nullptr) throw std::runtime_error("Could not find encoder");
  }

  void init_stream() {
    stream = avformat_new_stream(format_context, nullptr);
    if (stream == nullptr) throw std::runtime_error("Failed to alloc stream");
    stream->id = format_context->nb_streams - 1;
  }

  void init_codec_context() {
    codec_context = avcodec_alloc_context3(codec);
    if (codec_context == nullptr) throw std::runtime_error("Failed to alloc encoding context");

    auto codec_id = format_context->oformat->video_codec;
    codec_context->codec_id = codec_id;
    codec_context->bit_rate = 400000;
    codec_context->width = 640;
    codec_context->height = 480;
    stream->time_base = AVRational{1, 30};
    codec_context->time_base = stream->time_base;
    codec_context->gop_size = 30;  // one intra frame every gop_size
    // codec_context->max_b_frames = 0;  // output delayed by max_b_frames
    codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
    if (codec_context->codec_id == AV_CODEC_ID_MPEG1VIDEO) { codec_context->mb_decision = 2; }
    if (format_context->oformat->flags & AVFMT_GLOBALHEADER) {
      codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }
  }

  void init_frame() {
    frame = av_frame_alloc();
    if (frame == nullptr) throw std::runtime_error("Failed to alloc frame");
    frame->format = codec_context->pix_fmt;
    frame->width = codec_context->width;
    frame->height = codec_context->height;

    auto status = av_frame_get_buffer(frame, 32);
    if (status < 0) { throw std::runtime_error("Could not allocate frame data.\n"); }
  }

  void open_stream(const char* url) {
    int status = avcodec_open2(codec_context, codec, nullptr);
    if (status != 0) throw std::runtime_error("Failed to open codec");

    // copy the stream parameters to the muxer
    status = avcodec_parameters_from_context(stream->codecpar, codec_context);
    if (status < 0) throw std::runtime_error("Could not copy the stream parameters");

    av_dump_format(format_context, 0, url, 1);

    if (!(format_context->oformat->flags & AVFMT_NOFILE)) {
      status = avio_open(&format_context->pb, url, AVIO_FLAG_WRITE);
      if (status < 0) throw std::runtime_error("Could not open output file");
    }

    // Write the stream header, if any.
    status = avformat_write_header(format_context, nullptr);
    if (status < 0) throw std::runtime_error("Error occurred when opening output file");
  }

  AVStreamer(const char* url) : next_pts(0) {
    init_format_context(url);
    init_codec();
    init_stream();
    init_codec_context();
    init_frame();
    open_stream(url);
  }

  virtual ~AVStreamer() {
    avformat_free_context(format_context);
    avcodec_free_context(&codec_context);
    av_frame_free(&frame);
  }

  void send(cv::Mat const& image) {
    cv::cvtColor(image, image, CV_BGR2YUV);
    cv::Mat planes[3];
    cv::split(image, planes);
    cv::pyrDown(planes[1], planes[1]);
    cv::pyrDown(planes[2], planes[2]);

    if (av_frame_make_writable(frame) < 0) {
      throw std::runtime_error("Failed to make frame writable");
    }

    frame->data[0] = planes[0].data;
    frame->linesize[0] = planes[0].step;
    frame->data[1] = planes[1].data;
    frame->linesize[1] = planes[1].step;
    frame->data[2] = planes[2].data;
    frame->linesize[2] = planes[2].step;
    frame->pts = next_pts++;

    AVPacket packet;
    av_init_packet(&packet);

    int status = avcodec_send_frame(codec_context, frame);
    if (status < 0) throw std::runtime_error("Send frame failed");

    status = avcodec_receive_packet(codec_context, &packet);
    if (status == AVERROR(EAGAIN)) { return; }
    if (status < 0) throw std::runtime_error("Receive packet failed");

    av_packet_rescale_ts(&packet, codec_context->time_base, stream->time_base);
    packet.stream_index = stream->index;
    av_interleaved_write_frame(format_context, &packet);
  }
};

int main(int argc, char** argv) {
  av_register_all();
  avformat_network_init();

  auto url = argc == 2 ? argv[1] : "http://localhost:8081/supersecret";
  AVStreamer streamer(url);

  cv::VideoCapture video(0);
  assert(video.isOpened() && "Failed to open video");

  for (;;) {
    cv::Mat image;
    video >> image;
    streamer.send(image);
  }
}
...