J'essaye d'encoder une vidéo en H.264 avec libavcodec (version 3.4.6). Cela fonctionne lorsque j'utilise l'encodeur logiciel "libx264", mais ce n'est pas le cas lorsque j'essaye d'utiliser l'encodeur matériel de mon processeur Intel avec VAAPI. L'encodage matériel avec ffmpeg via VAAPI fonctionne à partir de la ligne de commande (en utilisant les commandes de ici ).
Apparemment, il n'y a pas d'exemple ou de tutoriel pour encoder avec VAAPI et libav *. J'ai lu ceux des exemples de ffmpeg qui couvrent un cas d'utilisation connexe ( décodage matériel, encodage logiciel, multiplexage) et j'ai essayé de les adapter en conséquence.
Lorsque je configure l'encodeur VAAPI, avcodec_open2 ()
renvoie AVERROR (EINVAL) code > (-22) et il imprime le message d'erreur suivant sur la console:
Incompatibilité AVCodecContext.pix_fmt et AVHWFramesContext.format
Vous pouvez le trouver à la fin de Encoder :: setupEncoder ()
dans mon code. Que me manque-t-il?
Voici mon code, qui est divisé en trois fichiers:
encoder.h
#include "encoder.h" AVFrame* createFrame(const int format) { AVFrame* frame = av_frame_alloc(); frame->format = format; frame->width = Encoder::s_width; frame->height = Encoder::s_height; assert(av_frame_get_buffer(frame, 0) == 0); assert(av_frame_make_writable(frame) == 0); // Y for(int y=0; y<frame->height; y++) { for(int x=0; x<frame->width; x++) { frame->data[0][y * frame->linesize[0] + x] = 0; } } // CbCr const int widthCbCr = frame->width / 2; const int heightCbCr = frame->height / 2; if(format == AV_PIX_FMT_YUV420P) { for(int y=0; y<heightCbCr; y++) { for(int x=0; x<widthCbCr; x++) { frame->data[1][y * frame->linesize[1] + x] = 0; // Cb frame->data[2][y * frame->linesize[2] + x] = 0; // Cr } } return frame; } else if(format == AV_PIX_FMT_NV12) { for(int y=0; y<heightCbCr; y++) { for(int x=0; x<widthCbCr; x++) { frame->data[1][y * frame->linesize[0] + x] = 0; } } return frame; } return nullptr; } int main() { av_register_all(); AVFrame* yuv420pFrame = createFrame(AV_PIX_FMT_YUV420P); AVFrame* nv12Frame = createFrame(AV_PIX_FMT_NV12); // works well Encoder softwareEncoder(false); for(int i=0; i<100; ++i) softwareEncoder.addFrame(yuv420pFrame); softwareEncoder.flush(); // does not work Encoder hardwareEncoder(true); for(int i=0; i<100; ++i) hardwareEncoder.addFrame(nv12Frame); hardwareEncoder.flush(); return 0; }
encoder .cpp
#include "encoder.h" extern "C" { static enum AVPixelFormat get_vaapi_format(AVCodecContext*, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { if (*p == AV_PIX_FMT_VAAPI) return *p; } fprintf(stderr, "Failed to get HW surface format.\n"); return AV_PIX_FMT_NONE; } } Encoder::Encoder(const bool hwAccel) : m_hardwareAcceleration(hwAccel) { setup(); } void Encoder::addFrame(AVFrame* frame) { AVFrame* frameToEncode = frame; if(m_hardwareAcceleration) { assert(frame->format == AV_PIX_FMT_NV12); av_hwframe_transfer_data(m_hwFrame, frame, 0); assert(m_hwFrame->format == AV_PIX_FMT_VAAPI); frameToEncode = m_hwFrame; } frameToEncode->pts = m_frameId++; encodeFrame(frameToEncode); } void Encoder::flush() { encodeFrame(nullptr); av_write_trailer(m_muxer); } void Encoder::setup() { assert(avformat_alloc_output_context2(&m_muxer, nullptr, "matroska", nullptr) == 0); assert(m_muxer != nullptr); setupEncoder(); m_avStream = avformat_new_stream(m_muxer, nullptr); assert(m_avStream != nullptr); m_avStream->id = m_muxer->nb_streams-1; m_avStream->time_base = m_encoder->time_base; // Some formats want stream headers to be separate. if(m_muxer->oformat->flags & AVFMT_GLOBALHEADER) m_encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; assert(avcodec_parameters_from_context(m_avStream->codecpar, m_encoder) == 0); assert(avio_open(&m_muxer->pb, m_hardwareAcceleration? "hardware.mkv": "software.mkv", AVIO_FLAG_WRITE) == 0); assert(avformat_write_header(m_muxer, nullptr) == 0); } void Encoder::setupEncoder() { const char* encoderName = m_hardwareAcceleration? "h264_vaapi": "libx264"; AVCodec* videoCodec = avcodec_find_encoder_by_name(encoderName); m_encoder = avcodec_alloc_context3(videoCodec); m_encoder->bit_rate = s_width * s_height * s_fps * 2; m_encoder->width = s_width; m_encoder->height = s_height; m_encoder->time_base = (AVRational){1, s_fps}; m_encoder->framerate = (AVRational){s_fps, 1}; m_encoder->gop_size = s_fps; // have at least 1 I-frame per second m_encoder->max_b_frames = 1; m_encoder->pix_fmt = AV_PIX_FMT_YUV420P; if(m_hardwareAcceleration) { m_encoder->pix_fmt = AV_PIX_FMT_VAAPI; m_encoder->get_format = get_vaapi_format; assert(av_hwdevice_ctx_create(&m_device, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0) == 0); AVHWDeviceContext* deviceCtx = (AVHWDeviceContext*) m_device->data; assert(deviceCtx->type == AV_HWDEVICE_TYPE_VAAPI); m_encoder->hw_device_ctx = av_hwframe_ctx_alloc(m_device); m_encoder->hw_frames_ctx = av_buffer_ref(m_device); m_hwFrame = av_frame_alloc(); av_hwframe_get_buffer(m_encoder->hw_device_ctx, m_hwFrame, 0); } assert(avcodec_open2(m_encoder, videoCodec, nullptr) == 0); // <-- returns -22 (EINVAL) for hardware encoder m_muxer->video_codec_id = videoCodec->id; m_muxer->video_codec = videoCodec; } void Encoder::encodeFrame(AVFrame* frame) { assert(avcodec_send_frame(m_encoder, frame) == 0); AVPacket packet; av_init_packet(&packet); int ret = 0; while(ret >= 0) { ret = avcodec_receive_packet(m_encoder, &packet); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return; // nothing to write } assert(ret >= 0); av_packet_rescale_ts(&packet, m_encoder->time_base, m_avStream->time_base); packet.stream_index = m_avStream->index; av_interleaved_write_frame(m_muxer, &packet); av_packet_unref(&packet); } }
main.cpp
#ifndef ENCODER_H #define ENCODER_H #include <cassert> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> #include <libavutil/hwcontext.h> } class Encoder { public: Encoder(const bool hwAccel); void addFrame(AVFrame* frame); void flush(); static constexpr int s_width = 640; static constexpr int s_height = 480; static constexpr int s_fps = 25; private: void setup(); void setupEncoder(); void encodeFrame(AVFrame* frame); // members int m_frameId = 1; const bool m_hardwareAcceleration = false; AVCodecContext* m_encoder = nullptr; AVFormatContext* m_muxer = nullptr; AVStream* m_avStream = nullptr; AVBufferRef* m_device = nullptr; AVFrame* m_hwFrame = nullptr; }; #endif // ENCODER_H
Notez que j'ai intentionnellement omis toutes sortes de fonctions et destructeurs free () pour garder le code court.
3 Réponses :
Il y a un grand commentaire dans le avcodec.h fichier où se trouve cet extrait (mal reproduit):
Le mélange d'appels de fonctions nouveaux et anciens sur le même AVCodecContext n'est pas autorisé,
et entraînera un comportement indéfini.
Certains codecs peuvent nécessiter l'utilisation de la nouvelle API; l'utilisation de l'ancienne API retournera
une erreur lors de son appel. Tous les codecs prennent en charge la nouvelle API.
Ceci (et le contenu environnant) suggère des raisons possibles pour lesquelles vous voyez l'erreur pour l'un, et pas pour l'autre.
Merci pour la réponse rapide! Existe-t-il une liste complète des fonctions obsolètes? La section des commentaires à laquelle vous m'avez indiqué ne mentionne que les fonctions directement impliquées dans le processus de codage proprement dit. Mon erreur se produit déjà à l'étape setup . J'ai oublié de mentionner que j'utilise la version 3.4.6 de libav *, qui est actuellement distribuée par Ubuntu 18.04 LTS.
@ gretel99 - Y a-t-il une liste complète des fonctions qui sont obsolètes? Je ne sais pas, mais vous pouvez Google aussi bien que je peux. Avez-vous parcouru le reste des commentaires. Il y en a quelques-uns, et certains d'entre eux peuvent finir par vous diriger vers d'autres sources d'informations. C'est un processus courant lors de l'utilisation de bibliothèques tierces ou même open source.
Je ne sais toujours pas ce qui ne va pas avec le code ci-dessus. Cependant, je l'ai fait fonctionner en déboguant ffmpeg et en imitant son comportement dans mon code. Au lieu de transférer manuellement la trame vers la mémoire GPU, ffmpeg utilise son framework de filtrage.
Pour les intéressés, voici mon code adapté à l'API de filtrage de ffmpeg (toujours sans libérer aucune ressource):
encodeur .h
#include "encoder.h" extern "C" { static enum AVPixelFormat get_vaapi_format(AVCodecContext*, const enum AVPixelFormat *pix_fmts) { const enum AVPixelFormat *p; for (p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { if (*p == AV_PIX_FMT_VAAPI) return *p; } fprintf(stderr, "Failed to get HW surface format.\n"); return AV_PIX_FMT_NONE; } } Encoder::Encoder(const bool hwAccel) : m_hardwareAcceleration(hwAccel) { setup(); } void Encoder::addFrame(AVFrame* frame) { AVFrame* frameToEncode = frame; if(m_hardwareAcceleration) { filterFrame(frame, m_hwFrame); assert(m_hwFrame->format == AV_PIX_FMT_VAAPI); frameToEncode = m_hwFrame; } frameToEncode->pts = m_frameId++; encodeFrame(frameToEncode); } void Encoder::flush() { encodeFrame(nullptr); av_write_trailer(m_muxer); } void Encoder::setup() { assert(avformat_alloc_output_context2(&m_muxer, nullptr, "matroska", nullptr) == 0); assert(m_muxer != nullptr); setupEncoder(); m_avStream = avformat_new_stream(m_muxer, nullptr); assert(m_avStream != nullptr); m_avStream->id = m_muxer->nb_streams-1; m_avStream->time_base = m_encoder->time_base; // Some formats want stream headers to be separate. if(m_muxer->oformat->flags & AVFMT_GLOBALHEADER) m_encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; assert(avcodec_parameters_from_context(m_avStream->codecpar, m_encoder) == 0); assert(avio_open(&m_muxer->pb, m_hardwareAcceleration? "hardware.mkv": "software.mkv", AVIO_FLAG_WRITE) == 0); assert(avformat_write_header(m_muxer, nullptr) == 0); } void Encoder::setupEncoder() { const char* encoderName = m_hardwareAcceleration? "h264_vaapi": "libx264"; AVCodec* videoCodec = avcodec_find_encoder_by_name(encoderName); m_encoder = avcodec_alloc_context3(videoCodec); m_encoder->bit_rate = s_width * s_height * s_fps * 2; m_encoder->width = s_width; m_encoder->height = s_height; m_encoder->time_base = (AVRational){1, s_fps}; m_encoder->framerate = (AVRational){s_fps, 1}; m_encoder->gop_size = s_fps; // have at least 1 I-frame per second m_encoder->max_b_frames = 1; m_encoder->pix_fmt = AV_PIX_FMT_YUV420P; if(m_hardwareAcceleration) { m_encoder->pix_fmt = AV_PIX_FMT_VAAPI; m_encoder->get_format = get_vaapi_format; assert(av_hwdevice_ctx_create(&m_device, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0) == 0); const AVHWDeviceContext* deviceCtx = (AVHWDeviceContext*) m_device->data; assert(deviceCtx->type == AV_HWDEVICE_TYPE_VAAPI); initFilters(); m_encoder->hw_device_ctx = nullptr; m_encoder->hw_frames_ctx = av_buffer_ref(av_buffersink_get_hw_frames_ctx(m_bufferSink)); } assert(avcodec_open2(m_encoder, videoCodec, nullptr) == 0); if(m_hardwareAcceleration) { m_encoder->hw_device_ctx = av_hwframe_ctx_alloc(m_device); m_hwFrame = av_frame_alloc(); av_hwframe_get_buffer(m_encoder->hw_device_ctx, m_hwFrame, 0); } m_muxer->video_codec_id = videoCodec->id; m_muxer->video_codec = videoCodec; } void Encoder::initFilters() { AVFilterInOut* inputs = nullptr; AVFilterInOut* outputs = nullptr; m_filterGraph = avfilter_graph_alloc(); assert(avfilter_graph_parse2(m_filterGraph, "format=nv12,hwupload", &inputs, &outputs) == 0); for(unsigned i=0; i<m_filterGraph->nb_filters; i++) { m_filterGraph->filters[i]->hw_device_ctx = av_buffer_ref(m_device); assert(m_filterGraph->filters[i]->hw_device_ctx != nullptr); } initInputFilters(inputs); initOutputFilters(outputs); assert(avfilter_graph_config(m_filterGraph, nullptr) == 0); } void Encoder::initInputFilters(AVFilterInOut* inputs) { assert(inputs != nullptr); assert(inputs->next == nullptr); char args[512]; snprintf(args, sizeof(args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d", s_width, s_height, AV_PIX_FMT_YUV420P, 1, s_fps, 1, 1); assert(avfilter_graph_create_filter(&m_bufferSrc, avfilter_get_by_name("buffer"), "in", args, nullptr, m_filterGraph) == 0); assert(avfilter_link(m_bufferSrc, 0, inputs->filter_ctx, inputs->pad_idx) == 0); } void Encoder::initOutputFilters(AVFilterInOut* outputs) { assert(outputs != nullptr); assert(outputs->next == nullptr); assert(avfilter_graph_create_filter(&m_bufferSink, avfilter_get_by_name("buffersink"), "out", nullptr, nullptr, m_filterGraph) == 0); assert(avfilter_graph_create_filter(&m_formatFilter, avfilter_get_by_name("format"), "format", "vaapi_vld", nullptr, m_filterGraph) == 0); assert(avfilter_link(outputs->filter_ctx, outputs->pad_idx, m_formatFilter, 0) == 0); assert(avfilter_link(m_formatFilter, 0, m_bufferSink, 0) == 0); } void Encoder::filterFrame(AVFrame* inFrame, AVFrame* outFrame) { assert(av_buffersrc_add_frame_flags(m_bufferSrc, inFrame, AV_BUFFERSRC_FLAG_KEEP_REF) == 0); assert(av_buffersink_get_frame(m_bufferSink, outFrame) == 0); } void Encoder::encodeFrame(AVFrame* frame) { assert(avcodec_send_frame(m_encoder, frame) == 0); AVPacket packet; av_init_packet(&packet); int ret = 0; while(ret >= 0) { ret = avcodec_receive_packet(m_encoder, &packet); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return; // nothing to write } assert(ret >= 0); av_packet_rescale_ts(&packet, m_encoder->time_base, m_avStream->time_base); packet.stream_index = m_avStream->index; av_interleaved_write_frame(m_muxer, &packet); av_packet_unref(&packet); } }
encoder.c
#ifndef ENCODER_H #define ENCODER_H #include <cassert> extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavutil/opt.h> #include <libavutil/hwcontext.h> #include <libavfilter/avfilter.h> #include <libavfilter/buffersink.h> #include <libavfilter/buffersrc.h> } class Encoder { public: Encoder(const bool hwAccel); void addFrame(AVFrame* frame); void flush(); static constexpr int s_width = 640; static constexpr int s_height = 480; static constexpr int s_fps = 25; private: void setup(); void setupEncoder(); void initFilters(); void initInputFilters(AVFilterInOut* inputs); void initOutputFilters(AVFilterInOut* outputs); void filterFrame(AVFrame* inFrame, AVFrame* outFrame); void encodeFrame(AVFrame* frame); // members int m_frameId = 1; const bool m_hardwareAcceleration = false; AVCodecContext* m_encoder = nullptr; AVFormatContext* m_muxer = nullptr; AVStream* m_avStream = nullptr; AVBufferRef* m_device = nullptr; AVFrame* m_hwFrame = nullptr; AVFilterGraph* m_filterGraph = nullptr; AVFilterContext* m_bufferSrc = nullptr; AVFilterContext* m_bufferSink = nullptr; AVFilterContext* m_formatFilter = nullptr; }; #endif // ENCODER_H
Peut-être un peu tard, mais pour tous ceux qui sont toujours intéressés, le code original (sans API de filtrage) fonctionne réellement, mais gretel99 a raté av_hwframe_ctx_alloc, voir l'exemple FFMmpeg dans doc / examples / vaapi_transcode.c .
Voici une version fixe de Encoder :: setupEncoder () avec des commentaires sur ce qui doit être changé. Ce n'est pas seulement l'allocation de tampon hwframe manquante, du moins pour mon pilote VAAPI rc_mode = CQP est requis et global_quality doit être défini pour fermer un avertissement.
void Encoder::setupEncoder() { const char* encoderName = m_hardwareAcceleration? "h264_vaapi": "libx264"; AVCodec* videoCodec = avcodec_find_encoder_by_name(encoderName); m_encoder = avcodec_alloc_context3(videoCodec); m_encoder->bit_rate = s_width * s_height * s_fps * 2; m_encoder->width = s_width; m_encoder->height = s_height; m_encoder->time_base = (AVRational){1, s_fps}; m_encoder->framerate = (AVRational){s_fps, 1}; m_encoder->gop_size = s_fps; // have at least 1 I-frame per second m_encoder->max_b_frames = 1; m_encoder->pix_fmt = AV_PIX_FMT_YUV420P; if(m_hardwareAcceleration) { m_encoder->pix_fmt = AV_PIX_FMT_VAAPI; m_encoder->get_format = get_vaapi_format; assert(av_hwdevice_ctx_create(&m_device, AV_HWDEVICE_TYPE_VAAPI, "/dev/dri/renderD128", nullptr, 0) == 0); AVHWDeviceContext* deviceCtx = (AVHWDeviceContext*) m_device->data; assert(deviceCtx->type == AV_HWDEVICE_TYPE_VAAPI); // Fix error: Mismatching AVCodecContext.pix_fmt and AVHWFramesContext.format // See doc/examples/vaapi_transcode.c "set_hwframe_ctx()" { AVBufferRef *hw_frames_ref; AVHWFramesContext *frames_ctx = NULL; assert((hw_frames_ref = av_hwframe_ctx_alloc(m_device)) != nullptr); frames_ctx = (AVHWFramesContext *)(hw_frames_ref->data); frames_ctx->format = AV_PIX_FMT_VAAPI; frames_ctx->sw_format = AV_PIX_FMT_NV12; frames_ctx->width = s_width; frames_ctx->height = s_height; frames_ctx->initial_pool_size = 20; assert(av_hwframe_ctx_init(hw_frames_ref) == 0); m_encoder->hw_frames_ctx = av_buffer_ref(hw_frames_ref); assert(m_encoder->hw_frames_ctx != nullptr); } // Fix error: Driver does not support any RC mode compatible with selected options (supported modes: CQP). assert(av_opt_set(m_encoder->priv_data, "rc_mode", "CQP", AV_OPT_SEARCH_CHILDREN) == 0); // Fix warning, cosmetical only: No quality level set; using default (20). m_encoder->global_quality = 20; m_encoder->hw_device_ctx = av_hwframe_ctx_alloc(m_device); //m_encoder->hw_frames_ctx = av_buffer_ref(m_device); // Fix: Not required, done by av_hwframe_ctx_alloc m_hwFrame = av_frame_alloc(); av_hwframe_get_buffer(m_encoder->hw_frames_ctx, m_hwFrame, 0); // Fix: Must pass hw_frames_ctx, not m_encoder->hw_device_ctx assert(m_hwFrame != nullptr); } assert(avcodec_open2(m_encoder, videoCodec, nullptr) == 0); // <-- returns -22 (EINVAL) for hardware encoder m_muxer->video_codec_id = videoCodec->id; m_muxer->video_codec = videoCodec; }