FFmpeg 硬编码VideoToolBox流程

news2024/9/28 22:29:58

介绍

  1. FFmpeg已经提供对 VideoToolBox 的编解码支持;主要涉及到的文件有videotoolbox.c、videotoolbox.h、videotoolboxenc.c、ffmepg_videotoolbox.c。
  2. 在编译 FFmpeg 源码时,想要支持VideoToolBox,在 configure 时,需要–enable-videotoolbox 命令。
  3. 命令行ffmpeg -hwaccels查看支持哪些硬编码器。
  4. ffmpeg 支持 videotoolbox h264 和 h265 的编码,即 h264_videotoolbox、hevc_videotoolbox。

FFmpeg

  1. FFmpeg 是一个可以处理音视频的软件,功能非常强大,主要包括,编解码转换,封装格式转换,滤镜特效。
  2. FFmpeg支持各种网络协议,支持 RTMP ,RTSP,HLS 等高层协议的推拉流,也支持更底层的TCP/UDP 协议推拉流。
  3. FFmpeg 可以在 Windows,Linux,Mac,iOS,Android等操作系统上运行。
  4. FFmpeg 是 " Fast Forward mpeg " 的缩写;
  5. FFMPEG从功能上划分为几个模块,分别为核心工具(libutils)、媒体格式(libavformat)、编解码(libavcodec)、设备(libavdevice)和后处理(libavfilter, libswscale, libpostproc),分别负责提供公用的功能函数、实现多媒体文件的读包和写包、完成音视频的编解码、管理音视频设备的操作以及进行音视频后处理。

VideoToolBox

  1. VideoToolBox是一个优化的视频编解码器框架,由苹果公司开发并针对iOS和macOS平台进行优化,作为现代移动应用程序中不可或缺的组成部分之一,它被用于H.264解码和编码,HEVC解码和编码,以及MPEG-2解码和编码,同时还支持对Core Audio和Core Video的访问。
  2. VideoToolBox的优点是高效性、易用性;在iOS和macOS设备上,它的编解码速度比其他框架要快得多;此外,它为开发人员提供了各种功能,包括修改视频帧速率,更改编码格式等等。

FFmpeg 硬编码 VideoToolBox 流程

  1. 可以看出,FFmpeg 与 VideoToolBox之间的交互,主要通过三个函数指针 init、encode2、close 来完成;
  2. 从整体流程分析,VideoToolBox 的工作流程是:
    创建 一个压缩会话 ;
    添加会话属性 ;
    编码视频帧、接受视频编码回调 ;
    强制完成一些或者全部未处理的视频帧;
    释放压缩会话、释放内存资源。
  3. init模块核心函数是 vtenc_configure_encode();
  4. encode2模块核心函数是vtenc_send_frame();
  5. close 模块的核心函数是VTCompressionSessionCompleteFrames();
    在这里插入图片描述

h264_videotoolbox

  1. VideoToolBox 的h264硬编码通过三个结构体h264_optionsh264_videotoolbox_classff_h264_videotoolbox_encoder来完成与 FFmpeg 的交互。
  2. h264_options主要涉及的是内部参数,例如 profile、level、熵编码选择等。
  3. h264_videotoolbox_class来定义 h264的私有类,指定编码类型和编码参数。
  4. ff_h264_videotoolbox_encoder是具体的对外与 FFmpeg 的交互结构体,完成h264硬编码。
static const AVOption h264_options[] = {
    { "profile", "Profile", OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = H264_PROF_AUTO }, H264_PROF_AUTO, H264_PROF_COUNT, VE, "profile" },
    { "baseline", "Baseline Profile", 0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_BASELINE }, INT_MIN, INT_MAX, VE, "profile" },
    { "main",     "Main Profile",     0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_MAIN     }, INT_MIN, INT_MAX, VE, "profile" },
    { "high",     "High Profile",     0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_HIGH     }, INT_MIN, INT_MAX, VE, "profile" },
    { "extended", "Extend Profile",   0, AV_OPT_TYPE_CONST, { .i64 = H264_PROF_EXTENDED }, INT_MIN, INT_MAX, VE, "profile" },

    { "level", "Level", OFFSET(level), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 52, VE, "level" },
    { "1.3", "Level 1.3, only available with Baseline Profile", 0, AV_OPT_TYPE_CONST, { .i64 = 13 }, INT_MIN, INT_MAX, VE, "level" },
    { "3.0", "Level 3.0", 0, AV_OPT_TYPE_CONST, { .i64 = 30 }, INT_MIN, INT_MAX, VE, "level" },
    { "3.1", "Level 3.1", 0, AV_OPT_TYPE_CONST, { .i64 = 31 }, INT_MIN, INT_MAX, VE, "level" },
    { "3.2", "Level 3.2", 0, AV_OPT_TYPE_CONST, { .i64 = 32 }, INT_MIN, INT_MAX, VE, "level" },
    { "4.0", "Level 4.0", 0, AV_OPT_TYPE_CONST, { .i64 = 40 }, INT_MIN, INT_MAX, VE, "level" },
    { "4.1", "Level 4.1", 0, AV_OPT_TYPE_CONST, { .i64 = 41 }, INT_MIN, INT_MAX, VE, "level" },
    { "4.2", "Level 4.2", 0, AV_OPT_TYPE_CONST, { .i64 = 42 }, INT_MIN, INT_MAX, VE, "level" },
    { "5.0", "Level 5.0", 0, AV_OPT_TYPE_CONST, { .i64 = 50 }, INT_MIN, INT_MAX, VE, "level" },
    { "5.1", "Level 5.1", 0, AV_OPT_TYPE_CONST, { .i64 = 51 }, INT_MIN, INT_MAX, VE, "level" },
    { "5.2", "Level 5.2", 0, AV_OPT_TYPE_CONST, { .i64 = 52 }, INT_MIN, INT_MAX, VE, "level" },

    { "coder", "Entropy coding", OFFSET(entropy), AV_OPT_TYPE_INT, { .i64 = VT_ENTROPY_NOT_SET }, VT_ENTROPY_NOT_SET, VT_CABAC, VE, "coder" },
    { "cavlc", "CAVLC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CAVLC }, INT_MIN, INT_MAX, VE, "coder" },
    { "vlc",   "CAVLC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CAVLC }, INT_MIN, INT_MAX, VE, "coder" },
    { "cabac", "CABAC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CABAC }, INT_MIN, INT_MAX, VE, "coder" },
    { "ac",    "CABAC entropy coding", 0, AV_OPT_TYPE_CONST, { .i64 = VT_CABAC }, INT_MIN, INT_MAX, VE, "coder" },

    { "a53cc", "Use A53 Closed Captions (if available)", OFFSET(a53_cc), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, VE },

    COMMON_OPTIONS
    { NULL },
};

static const AVClass h264_videotoolbox_class = {
    .class_name = "h264_videotoolbox",
    .item_name  = av_default_item_name,
    .option     = h264_options,
    .version    = LIBAVUTIL_VERSION_INT,
};

AVCodec ff_h264_videotoolbox_encoder = {
    .name             = "h264_videotoolbox",
    .long_name        = NULL_IF_CONFIG_SMALL("VideoToolbox H.264 Encoder"),
    .type             = AVMEDIA_TYPE_VIDEO,
    .id               = AV_CODEC_ID_H264,
    .priv_data_size   = sizeof(VTEncContext),
    .pix_fmts         = avc_pix_fmts,
    .init             = vtenc_init,
    .encode2          = vtenc_frame,
    .close            = vtenc_close,
    .capabilities     = AV_CODEC_CAP_DELAY,
    .priv_class       = &h264_videotoolbox_class,
    .caps_internal    = FF_CODEC_CAP_INIT_THREADSAFE |
                        FF_CODEC_CAP_INIT_CLEANUP,
};

hevc_videotoolbox

  1. VideoToolBox 的HEVC硬编码通过三个结构体hevc_optionshevc_videotoolbox_classff_hevc_videotoolbox_encoder来完成与 FFmpeg 的交互。
  2. hevc_options主要涉及的是内部参数,例如 profile的选择。
  3. hevc_videotoolbox_class来定义 HEVC的私有类,指定编码类型和编码参数。
  4. ff_hevc_videotoolbox_encoder是具体的对外与 FFmpeg 的交互结构体,完成HEVC硬编码。
static const AVOption hevc_options[] = {
    { "profile", "Profile", OFFSET(profile), AV_OPT_TYPE_INT, { .i64 = HEVC_PROF_AUTO }, HEVC_PROF_AUTO, HEVC_PROF_COUNT, VE, "profile" },
    { "main",     "Main Profile",     0, AV_OPT_TYPE_CONST, { .i64 = HEVC_PROF_MAIN   }, INT_MIN, INT_MAX, VE, "profile" },
    { "main10",   "Main10 Profile",   0, AV_OPT_TYPE_CONST, { .i64 = HEVC_PROF_MAIN10 }, INT_MIN, INT_MAX, VE, "profile" },

    COMMON_OPTIONS
    { NULL },
};

static const AVClass hevc_videotoolbox_class = {
    .class_name = "hevc_videotoolbox",
    .item_name  = av_default_item_name,
    .option     = hevc_options,
    .version    = LIBAVUTIL_VERSION_INT,
};

AVCodec ff_hevc_videotoolbox_encoder = {
    .name             = "hevc_videotoolbox",
    .long_name        = NULL_IF_CONFIG_SMALL("VideoToolbox H.265 Encoder"),
    .type             = AVMEDIA_TYPE_VIDEO,
    .id               = AV_CODEC_ID_HEVC,
    .priv_data_size   = sizeof(VTEncContext),
    .pix_fmts         = hevc_pix_fmts,
    .init             = vtenc_init,
    .encode2          = vtenc_frame,
    .close            = vtenc_close,
    .capabilities     = AV_CODEC_CAP_DELAY | AV_CODEC_CAP_HARDWARE,
    .priv_class       = &hevc_videotoolbox_class,
    .caps_internal    = FF_CODEC_CAP_INIT_THREADSAFE |
                        FF_CODEC_CAP_INIT_CLEANUP,
    .wrapper_name     = "videotoolbox",
};

核心模块介绍

.init

  1. .init模块完成初始化工作,对应的函数是vtenc_init();函数内部主要完成了线程初始化、配置编码器、检索属性以及 B 帧的相关处理。
    在这里插入图片描述
static av_cold int vtenc_init(AVCodecContext *avctx)
{
    VTEncContext    *vtctx = avctx->priv_data;
    CFBooleanRef    has_b_frames_cfbool;
    int             status;

    pthread_once(&once_ctrl, loadVTEncSymbols);

    pthread_mutex_init(&vtctx->lock, NULL);
    pthread_cond_init(&vtctx->cv_sample_sent, NULL);

    vtctx->session = NULL;
    status = vtenc_configure_encoder(avctx);
    if (status) return status;

    status = VTSessionCopyProperty(vtctx->session,
                                   kVTCompressionPropertyKey_AllowFrameReordering,
                                   kCFAllocatorDefault,
                                   &has_b_frames_cfbool);

    if (!status && has_b_frames_cfbool) {
        //Some devices don't output B-frames for main profile, even if requested.
        vtctx->has_b_frames = CFBooleanGetValue(has_b_frames_cfbool);
        CFRelease(has_b_frames_cfbool);
    }
    avctx->has_b_frames = vtctx->has_b_frames;

    return 0;
}
  1. vtenc_configure_encoder()函数是 init 模块的核心函数,主要完成编码器的配置工作;根据编码器类型(h264/HEVC)来配置 profile、level、熵编码等信息;此外还会选择裁剪信息、传递函数、YCbCr 矩阵、颜色原色以及额外信息;最后调用vtenc_create_encoder()完成编码器的创建;
    在这里插入图片描述
static int vtenc_configure_encoder(AVCodecContext *avctx)
{
    CFMutableDictionaryRef enc_info;
    CFMutableDictionaryRef pixel_buffer_info;
    CMVideoCodecType       codec_type;
    VTEncContext           *vtctx = avctx->priv_data;
    CFStringRef            profile_level;
    CFNumberRef            gamma_level = NULL;
    int                    status;

    codec_type = get_cm_codec_type(avctx->codec_id);
    if (!codec_type) {
        av_log(avctx, AV_LOG_ERROR, "Error: no mapping for AVCodecID %d\n", avctx->codec_id);
        return AVERROR(EINVAL);
    }

    vtctx->codec_id = avctx->codec_id;

    if (vtctx->codec_id == AV_CODEC_ID_H264) {
        vtctx->get_param_set_func = CMVideoFormatDescriptionGetH264ParameterSetAtIndex;

        vtctx->has_b_frames = avctx->max_b_frames > 0;
        if(vtctx->has_b_frames && vtctx->profile == H264_PROF_BASELINE){
            av_log(avctx, AV_LOG_WARNING, "Cannot use B-frames with baseline profile. Output will not contain B-frames.\n");
            vtctx->has_b_frames = false;
        }

        if (vtctx->entropy == VT_CABAC && vtctx->profile == H264_PROF_BASELINE) {
            av_log(avctx, AV_LOG_WARNING, "CABAC entropy requires 'main' or 'high' profile, but baseline was requested. Encode will not use CABAC entropy.\n");
            vtctx->entropy = VT_ENTROPY_NOT_SET;
        }

        if (!get_vt_h264_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
    } else {
        vtctx->get_param_set_func = compat_keys.CMVideoFormatDescriptionGetHEVCParameterSetAtIndex;
        if (!vtctx->get_param_set_func) return AVERROR(EINVAL);
        if (!get_vt_hevc_profile_level(avctx, &profile_level)) return AVERROR(EINVAL);
    }

    enc_info = CFDictionaryCreateMutable(
        kCFAllocatorDefault,
        20,
        &kCFCopyStringDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks
    );

    if (!enc_info) return AVERROR(ENOMEM);

#if !TARGET_OS_IPHONE
    if(vtctx->require_sw) {
        CFDictionarySetValue(enc_info,
                             compat_keys.kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
                             kCFBooleanFalse);
    } else if (!vtctx->allow_sw) {
        CFDictionarySetValue(enc_info,
                             compat_keys.kVTVideoEncoderSpecification_RequireHardwareAcceleratedVideoEncoder,
                             kCFBooleanTrue);
    } else {
        CFDictionarySetValue(enc_info,
                             compat_keys.kVTVideoEncoderSpecification_EnableHardwareAcceleratedVideoEncoder,
                             kCFBooleanTrue);
    }
#endif

    if (avctx->pix_fmt != AV_PIX_FMT_VIDEOTOOLBOX) {
        status = create_cv_pixel_buffer_info(avctx, &pixel_buffer_info);
        if (status)
            goto init_cleanup;
    } else {
        pixel_buffer_info = NULL;
    }

    vtctx->dts_delta = vtctx->has_b_frames ? -1 : 0;

    get_cv_transfer_function(avctx, &vtctx->transfer_function, &gamma_level);
    get_cv_ycbcr_matrix(avctx, &vtctx->ycbcr_matrix);
    get_cv_color_primaries(avctx, &vtctx->color_primaries);


    if (avctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER) {
        status = vtenc_populate_extradata(avctx,
                                          codec_type,
                                          profile_level,
                                          gamma_level,
                                          enc_info,
                                          pixel_buffer_info);
        if (status)
            goto init_cleanup;
    }

    status = vtenc_create_encoder(avctx,
                                  codec_type,
                                  profile_level,
                                  gamma_level,
                                  enc_info,
                                  pixel_buffer_info,
                                  &vtctx->session);

init_cleanup:
    if (gamma_level)
        CFRelease(gamma_level);

    if (pixel_buffer_info)
        CFRelease(pixel_buffer_info);

    CFRelease(enc_info);

    return status;
}
  1. vtenc_create_encoder()完成编码器创建工作;调用VTCompressionSessionCreate()创建压缩帧实例,接着会创建码率/码控等各类对象,并配置相应属性;最后,(可选)调用VTCompressionSessionPrepareToEncodeFrames()完成编码前的合理资源分配。
    在这里插入图片描述

.encode2

  1. .encode2模块完成具体的编码工作,对应的函数是 vtenc_frame();判断 AVFrame里是否有帧数据,有数据就调用vtenc_send_frame()完成具体的编码,没有就 flush 下;然后调用 vtenc_q_pop()完成线程相关操作;最后利用vtenc_cm_to_avpacket()得到数据包信息,如 SEI、pts、dts 等。
    在这里插入图片描述
static av_cold int vtenc_frame(
    AVCodecContext *avctx,
    AVPacket       *pkt,
    const AVFrame  *frame,
    int            *got_packet)
{
    VTEncContext *vtctx = avctx->priv_data;
    bool get_frame;
    int status;
    CMSampleBufferRef buf = NULL;
    ExtraSEI *sei = NULL;

    if (frame) {
        status = vtenc_send_frame(avctx, vtctx, frame);

        if (status) {
            status = AVERROR_EXTERNAL;
            goto end_nopkt;
        }

        if (vtctx->frame_ct_in == 0) {
            vtctx->first_pts = frame->pts;
        } else if(vtctx->frame_ct_in == 1 && vtctx->has_b_frames) {
            vtctx->dts_delta = frame->pts - vtctx->first_pts;
        }

        vtctx->frame_ct_in++;
    } else if(!vtctx->flushing) {
        vtctx->flushing = true;

        status = VTCompressionSessionCompleteFrames(vtctx->session,
                                                    kCMTimeIndefinite);

        if (status) {
            av_log(avctx, AV_LOG_ERROR, "Error flushing frames: %d\n", status);
            status = AVERROR_EXTERNAL;
            goto end_nopkt;
        }
    }

    *got_packet = 0;
    get_frame = vtctx->dts_delta >= 0 || !frame;
    if (!get_frame) {
        status = 0;
        goto end_nopkt;
    }

    status = vtenc_q_pop(vtctx, !frame, &buf, &sei);
    if (status) goto end_nopkt;
    if (!buf)   goto end_nopkt;

    status = vtenc_cm_to_avpacket(avctx, buf, pkt, sei);
    if (sei) {
        if (sei->data) av_free(sei->data);
        av_free(sei);
    }
    CFRelease(buf);
    if (status) goto end_nopkt;

    *got_packet = 1;
    return 0;

end_nopkt:
    av_packet_unref(pkt);
    return status;
}
  1. vtenc_send_frame()完成编码核心工作;内部主要调用 VideoToolBox 的核心 API函数VTCompressionSessionEncodeFrame()完成具体的编码工作。
    在这里插入图片描述
static int vtenc_send_frame(AVCodecContext *avctx,
                            VTEncContext   *vtctx,
                            const AVFrame  *frame)
{
    CMTime time;
    CFDictionaryRef frame_dict;
    CVPixelBufferRef cv_img = NULL;
    AVFrameSideData *side_data = NULL;
    ExtraSEI *sei = NULL;
    int status = create_cv_pixel_buffer(avctx, frame, &cv_img);

    if (status) return status;

    status = create_encoder_dict_h264(frame, &frame_dict);
    if (status) {
        CFRelease(cv_img);
        return status;
    }

    side_data = av_frame_get_side_data(frame, AV_FRAME_DATA_A53_CC);
    if (vtctx->a53_cc && side_data && side_data->size) {
        sei = av_mallocz(sizeof(*sei));
        if (!sei) {
            av_log(avctx, AV_LOG_ERROR, "Not enough memory for closed captions, skipping\n");
        } else {
            int ret = ff_alloc_a53_sei(frame, 0, &sei->data, &sei->size);
            if (ret < 0) {
                av_log(avctx, AV_LOG_ERROR, "Not enough memory for closed captions, skipping\n");
                av_free(sei);
                sei = NULL;
            }
        }
    }

    time = CMTimeMake(frame->pts * avctx->time_base.num, avctx->time_base.den);
    status = VTCompressionSessionEncodeFrame(
        vtctx->session,
        cv_img,
        time,
        kCMTimeInvalid,
        frame_dict,
        sei,
        NULL
    );

    if (frame_dict) CFRelease(frame_dict);
    CFRelease(cv_img);

    if (status) {
        av_log(avctx, AV_LOG_ERROR, "Error: cannot encode frame: %d\n", status);
        return AVERROR_EXTERNAL;
    }

    return 0;
}

.close

  1. .close 模块完成关闭回收工作,对应的函数是 vtenc_close();内部主要进行线程的销毁、强制完成一些或全部未处理的视频帧、清除帧队列、释放资源的工作。
    在这里插入图片描述
static av_cold int vtenc_close(AVCodecContext *avctx)
{
    VTEncContext *vtctx = avctx->priv_data;

    pthread_cond_destroy(&vtctx->cv_sample_sent);
    pthread_mutex_destroy(&vtctx->lock);

    if(!vtctx->session) return 0;

    VTCompressionSessionCompleteFrames(vtctx->session,
                                       kCMTimeIndefinite);
    clear_frame_queue(vtctx);
    CFRelease(vtctx->session);
    vtctx->session = NULL;

    if (vtctx->color_primaries) {
        CFRelease(vtctx->color_primaries);
        vtctx->color_primaries = NULL;
    }

    if (vtctx->transfer_function) {
        CFRelease(vtctx->transfer_function);
        vtctx->transfer_function = NULL;
    }

    if (vtctx->ycbcr_matrix) {
        CFRelease(vtctx->ycbcr_matrix);
        vtctx->ycbcr_matrix = NULL;
    }

    return 0;
}

参考

  1. https://developer.apple.com/documentation/videotoolbox
  2. http://ffmpeg.org/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/852900.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SMART司马他法则(目标管理)

S代表具体(Specific)&#xff0c;指绩效考核要切中特定的工作指标&#xff0c;不能笼统&#xff1b; M代表可度量(Measurable)&#xff0c;指绩效指标是数量化或者行为化的&#xff0c;验证这些绩效指标的数据或者信息是可以获得的&#xff1b; A代表可实现(Attainable)&…

CSV文件编辑器——Modern CSV for mac

Modern CSV for Mac是一款功能强大、操作简单的CSV文件编辑器&#xff0c;适用于Mac用户快速、高效地处理和管理CSV文件。Modern CSV具有直观的用户界面&#xff0c;可以轻松导入、编辑和导出CSV文件。它支持各种功能&#xff0c;包括排序、过滤、查找和替换&#xff0c;使您能…

1.RuoYi-Vue前后端分离版本启动

1.代码下载 去若依官网&#xff0c;选择RuoYI前后端分离版: 下载地址&#xff1a;https://gitee.com/y_project/RuoYi-Vue 2.依赖环境的部署 1.Mysql 2.Redis安装部署 : https://blog.csdn.net/qq_27860623/article/details/132168382 3.Idea打开后端服务 用Idea打开后端的…

隐秘通信-使用PingTunnel搭建ICMP隧道实验

目录 引言 实验目的 实验内容 基础知识 ICMP协议简介 ICMP隧道简介 ICMP隧道搭建工具介绍 PingTunnel简介 实验过程 前置准备 PingTunnel搭建ICMP隧道步骤 CentOS 7更换阿里源 CentOS 7安装make及词法分析工具 CentOS 7安装libpcap依赖库 CentOS 7安装PingTunnel…

Scikit-learn聚类方法代码批注及相关练习

一、代码批注 代码来自&#xff1a;https://scikit-learn.org/stable/auto_examples/cluster/plot_dbscan.html#sphx-glr-auto-examples-cluster-plot-dbscan-py import numpy as np from sklearn.cluster import DBSCAN from sklearn import metrics from sklearn.datasets …

机器学习深度学习——RNN的从零开始实现与简洁实现

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——循环神经网络RNN &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮…

php后端实现调用高德地图进行POI搜索

对于当前位置或者选定省市位置进行查询 接口实现 /*** 查询地址* ApiTitle (查询地址)* ApiSummary (查询地址)* ApiMethod (POST)* ApiRoute (/api/demo/address)* ApiParams (name"dart", type"integer", requiredtrue, description"省…

项目经理的性格与情绪控制︱小象智能COO、原腾讯项目集管理经理王炼

小象智能COO、原腾讯项目集管理经理王炼女士受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;项目经理的性格与情绪控制。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; 众所周知&#xff0c;项…

光模块故障:能否继续发射光信号?

光模块是一种关键的光通信组件&#xff0c;负责将电信号转换为光信号进行传输。然而&#xff0c;光模块也可能出现故障&#xff0c;导致其无法正常工作。那么&#xff0c;如果光模块坏了&#xff0c;是否还能发射光信号呢&#xff1f;本文将探讨光模块故障对光信号发射的影响&a…

【ArcGIS】经纬度数据转化成平面坐标数据

将点位置导入Gis中&#xff0c;如下&#xff08;经纬度表征位置&#xff09;&#xff1a; 如何利用Gis将其转化为平面坐标呢&#xff1f; Step1 坐标变换 坐标变换&#xff0c;打开ArcToolbox&#xff0c;找到“数据管理工具”->“投影和变换”->“要素”->“投影”…

0基础学习VR全景平台篇 第81篇:全景相机-临云镜如何直播推流

临云镜全景相机是阿里巴巴定制全景设备&#xff0c;实现空间三维信息的快速采集&#xff0c;与阿里云三维空间重建平台搭配&#xff0c;帮助品牌商与平台以较低的成本完成空间的快速采集&#xff0c;并支持对室内/室外空间的三维全景展示及空间漫游&#xff0c;同时支持VR浏览、…

论文图表--pyecharts使用

python中使用matplotlib画图颜色不好看&#xff0c;所以选取了pyecharts来画图表。 echarts介绍 pyecharts是echarts的python接口&#xff0c;echarts本身是基于javascript的&#xff0c;由于javascript搭建也比较麻烦&#xff0c;这里使用pyecharts来画图。 echarts常用来pp…

语音同声翻译软件到底谁更胜一筹呢

嘿&#xff01;你是否曾经遇到过需要在不同语言之间进行实时翻译的情况&#xff1f;别担心&#xff0c;现在有许多翻译软件可供选择&#xff0c;让你的沟通变得更加简便和愉快。无论你是旅行者、国际商务人士还是语言爱好者&#xff0c;这些软件都将成为你的得力助手&#xff0…

PyQt学习笔记-Windows系统版本兼容问题踩坑记录

1 Pyinstaller打包的exe在Win10上可以使用&#xff0c;在Win7上缺提示找不到dll。 错误信息&#xff1a; Traceback (most recent call last): File "main.py", line 4, in <module> ImportError: DLL load failed while importing QtWidgets: 找不到指定的…

Beautiful Sequence 2023牛客暑期多校训练营7 C

登录—专业IT笔试面试备考平台_牛客网 题目大意&#xff1a;给出一长度n-1的数组b&#xff0c;要求构造一个长度为n的数组a&#xff0c;满足a[i]^a[i1]b[i]&#xff0c;a[i]<a[i1]&#xff0c;求字典序第k小的数组 1<n<1e6;0<a[i],b[i]< 思路&#xff1a;因为…

快速引流推广,快速引流推广策略分享,教你精准引流

科思创业汇 大家好&#xff0c;这里是科思创业汇&#xff0c;一个轻资产创业孵化平台。赚钱的方式有很多种&#xff0c;我希望在科思创业汇能够给你带来最快乐的那一种&#xff01; 在当今互联网的快速发展中&#xff0c;短视频脱颖而出&#xff0c;成为互联网的新秀&#xf…

Python-OpenCV中的图像处理-几何变换

Python-OpenCV中的图像处理-几何变换 几何变换图像缩放图像平移图像旋转仿射变换透视变换 几何变换 对图像进行各种几个变换&#xff0c;例如移动&#xff0c;旋转&#xff0c;仿射变换等。 图像缩放 cv2.resize() cv2.INTER_AREAv2.INTER_CUBICv2.INTER_LINEAR res cv2.r…

PDCA、4Y(循环质量管理)

PDCA是什么&#xff1f; PDCA是由英语单词Plan(计划)、Do(执行)、Check(检查)和Act(修正)的第一个字母组成的&#xff0c;PDCA是按照这样的顺序进行质量管理的循环。PDCA四个英文字母及其在PDCA循环中所代表的含义如下&#xff1a; P&#xff08;Plan&#xff09;&#xff1a…

做电商的老哥注意,不干的淘宝产品记得下架,别被薅羊毛了

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 提醒所有电商人&#xff1a;不干了就把链接下架&#xff0c;别被薅羊毛了。 关注卢松松的人都知道&#xff0c;虽然卢松松不做电商&#xff0c;但我还是有好几个店铺的。6月份我的某一个僵尸店铺就…

PROFIBUS-DP主站转ETHERCAT网关连接西门子支持ethercat吗

大家好&#xff0c;今天要给大家介绍一款捷米的神秘产品&#xff0c;它的名字叫JM-DPM-ECT&#xff0c;是一款兼具PROFIBUS-DP主站功能的通讯网关。想象一下&#xff0c;它既能和PROFIBUS总线打交道&#xff0c;又能与ETHERCAT网络愉快地交流&#xff0c;是不是感觉很神奇&…