音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现

news2024/11/14 20:22:00

 =================================================================

音视频入门基础:WAV专题系列文章:

音视频入门基础:WAV专题(1)——使用FFmpeg命令生成WAV音频文件

音视频入门基础:WAV专题(2)——WAV格式简介

音视频入门基础:WAV专题(3)——FFmpeg源码中,判断某文件是否为WAV音频文件的实现

音视频入门基础:WAV专题(4)——FFmpeg源码中获取WAV文件音频压缩编码格式、采样频率、声道数量、采样位数、码率的实现

音视频入门基础:WAV专题(5)——FFmpeg源码中解码WAV Header的实现

=================================================================

一、引言

执行FFmpeg命令:

./ffmpeg -i XXX.wav

FFmpeg内部会调用wav_probe函数检测该文件是否为WAV格式的音频文件(具体可以参考:《音视频入门基础:WAV专题(3)——FFmpeg源码中,判断某文件是否为WAV音频文件的实现》)。然后如果检测出该文件为WAV格式的音频文件,会调用wav_read_header函数解码WAV Header。

二、wav_read_header函数的定义

wav_read_header函数定义在FFmpeg源码(本文演示用的FFmpeg源码版本为5.0.3)的源文件libavformat/wavdec.c中:

/* wav input */
static int wav_read_header(AVFormatContext *s)
{
    int64_t size, av_uninit(data_size);
    int64_t sample_count = 0;
    int rf64 = 0, bw64 = 0;
    uint32_t tag;
    AVIOContext *pb      = s->pb;
    AVStream *st         = NULL;
    WAVDemuxContext *wav = s->priv_data;
    int ret, got_fmt = 0, got_xma2 = 0;
    int64_t next_tag_ofs, data_ofs = -1;

    wav->unaligned = avio_tell(s->pb) & 1;

    wav->smv_data_ofs = -1;

    /* read chunk ID */
    tag = avio_rl32(pb);
    switch (tag) {
    case MKTAG('R', 'I', 'F', 'F'):
        break;
    case MKTAG('R', 'I', 'F', 'X'):
        wav->rifx = 1;
        break;
    case MKTAG('R', 'F', '6', '4'):
        rf64 = 1;
        break;
    case MKTAG('B', 'W', '6', '4'):
        bw64 = 1;
        break;
    default:
        av_log(s, AV_LOG_ERROR, "invalid start code %s in RIFF header\n",
               av_fourcc2str(tag));
        return AVERROR_INVALIDDATA;
    }

    /* read chunk size */
    avio_rl32(pb);

    /* read format */
    if (avio_rl32(pb) != MKTAG('W', 'A', 'V', 'E')) {
        av_log(s, AV_LOG_ERROR, "invalid format in RIFF header\n");
        return AVERROR_INVALIDDATA;
    }

    if (rf64 || bw64) {
        if (avio_rl32(pb) != MKTAG('d', 's', '6', '4'))
            return AVERROR_INVALIDDATA;
        size = avio_rl32(pb);
        if (size < 24)
            return AVERROR_INVALIDDATA;
        avio_rl64(pb); /* RIFF size */

        data_size    = avio_rl64(pb);
        sample_count = avio_rl64(pb);

        if (data_size < 0 || sample_count < 0) {
            av_log(s, AV_LOG_ERROR, "negative data_size and/or sample_count in "
                   "ds64: data_size = %"PRId64", sample_count = %"PRId64"\n",
                   data_size, sample_count);
            return AVERROR_INVALIDDATA;
        }
        avio_skip(pb, size - 24); /* skip rest of ds64 chunk */

    }

    /* Create the audio stream now so that its index is always zero */
    st = avformat_new_stream(s, NULL);
    if (!st)
        return AVERROR(ENOMEM);

    for (;;) {
        AVStream *vst;
        size         = next_tag(pb, &tag, wav->rifx);
        next_tag_ofs = avio_tell(pb) + size;

        if (avio_feof(pb))
            break;

        switch (tag) {
        case MKTAG('f', 'm', 't', ' '):
            /* only parse the first 'fmt ' tag found */
            if (!got_xma2 && !got_fmt && (ret = wav_parse_fmt_tag(s, size, st)) < 0) {
                return ret;
            } else if (got_fmt)
                av_log(s, AV_LOG_WARNING, "found more than one 'fmt ' tag\n");

            got_fmt = 1;
            break;
        case MKTAG('X', 'M', 'A', '2'):
            /* only parse the first 'XMA2' tag found */
            if (!got_fmt && !got_xma2 && (ret = wav_parse_xma2_tag(s, size, st)) < 0) {
                return ret;
            } else if (got_xma2)
                av_log(s, AV_LOG_WARNING, "found more than one 'XMA2' tag\n");

            got_xma2 = 1;
            break;
        case MKTAG('d', 'a', 't', 'a'):
            if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) && !got_fmt && !got_xma2) {
                av_log(s, AV_LOG_ERROR,
                       "found no 'fmt ' tag before the 'data' tag\n");
                return AVERROR_INVALIDDATA;
            }

            if (rf64 || bw64) {
                next_tag_ofs = wav->data_end = avio_tell(pb) + data_size;
            } else if (size != 0xFFFFFFFF) {
                data_size    = size;
                next_tag_ofs = wav->data_end = size ? next_tag_ofs : INT64_MAX;
            } else {
                av_log(s, AV_LOG_WARNING, "Ignoring maximum wav data size, "
                       "file may be invalid\n");
                data_size    = 0;
                next_tag_ofs = wav->data_end = INT64_MAX;
            }

            data_ofs = avio_tell(pb);

            /* don't look for footer metadata if we can't seek or if we don't
             * know where the data tag ends
             */
            if (!(pb->seekable & AVIO_SEEKABLE_NORMAL) || (!(rf64 && !bw64) && !size))
                goto break_loop;
            break;
        case MKTAG('f', 'a', 'c', 't'):
            if (!sample_count)
                sample_count = (!wav->rifx ? avio_rl32(pb) : avio_rb32(pb));
            break;
        case MKTAG('b', 'e', 'x', 't'):
            if ((ret = wav_parse_bext_tag(s, size)) < 0)
                return ret;
            break;
        case MKTAG('S','M','V','0'):
            if (!got_fmt) {
                av_log(s, AV_LOG_ERROR, "found no 'fmt ' tag before the 'SMV0' tag\n");
                return AVERROR_INVALIDDATA;
            }
            // SMV file, a wav file with video appended.
            if (size != MKTAG('0','2','0','0')) {
                av_log(s, AV_LOG_ERROR, "Unknown SMV version found\n");
                goto break_loop;
            }
            av_log(s, AV_LOG_DEBUG, "Found SMV data\n");
            wav->smv_given_first = 0;
            vst = avformat_new_stream(s, NULL);
            if (!vst)
                return AVERROR(ENOMEM);
            wav->vst = vst;
            avio_r8(pb);
            vst->id = 1;
            vst->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
            vst->codecpar->codec_id = AV_CODEC_ID_SMVJPEG;
            vst->codecpar->width  = avio_rl24(pb);
            vst->codecpar->height = avio_rl24(pb);
            if ((ret = ff_alloc_extradata(vst->codecpar, 4)) < 0) {
                av_log(s, AV_LOG_ERROR, "Could not allocate extradata.\n");
                return ret;
            }
            size = avio_rl24(pb);
            wav->smv_data_ofs = avio_tell(pb) + (size - 5) * 3;
            avio_rl24(pb);
            wav->smv_block_size = avio_rl24(pb);
            if (!wav->smv_block_size)
                return AVERROR_INVALIDDATA;
            avpriv_set_pts_info(vst, 32, 1, avio_rl24(pb));
            vst->duration = avio_rl24(pb);
            avio_rl24(pb);
            avio_rl24(pb);
            wav->smv_frames_per_jpeg = avio_rl24(pb);
            if (wav->smv_frames_per_jpeg > 65536) {
                av_log(s, AV_LOG_ERROR, "too many frames per jpeg\n");
                return AVERROR_INVALIDDATA;
            }
            AV_WL32(vst->codecpar->extradata, wav->smv_frames_per_jpeg);
            goto break_loop;
        case MKTAG('L', 'I', 'S', 'T'):
        case MKTAG('l', 'i', 's', 't'):
            if (size < 4) {
                av_log(s, AV_LOG_ERROR, "too short LIST tag\n");
                return AVERROR_INVALIDDATA;
            }
            switch (avio_rl32(pb)) {
            case MKTAG('I', 'N', 'F', 'O'):
                ff_read_riff_info(s, size - 4);
                break;
            case MKTAG('a', 'd', 't', 'l'):
                if (s->nb_chapters > 0) {
                    while (avio_tell(pb) < next_tag_ofs &&
                           !avio_feof(pb)) {
                        char cue_label[512];
                        unsigned id, sub_size;

                        if (avio_rl32(pb) != MKTAG('l', 'a', 'b', 'l'))
                            break;

                        sub_size = avio_rl32(pb);
                        if (sub_size < 5)
                            break;
                        id       = avio_rl32(pb);
                        avio_get_str(pb, sub_size - 4, cue_label, sizeof(cue_label));
                        avio_skip(pb, avio_tell(pb) & 1);

                        for (int i = 0; i < s->nb_chapters; i++) {
                            if (s->chapters[i]->id == id) {
                                av_dict_set(&s->chapters[i]->metadata, "title", cue_label, 0);
                                break;
                            }
                        }
                    }
                }
                break;
            }
            break;
        case MKTAG('I', 'D', '3', ' '):
        case MKTAG('i', 'd', '3', ' '): {
            ID3v2ExtraMeta *id3v2_extra_meta;
            ff_id3v2_read_dict(pb, &ffformatcontext(s)->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta);
            if (id3v2_extra_meta) {
                ff_id3v2_parse_apic(s, id3v2_extra_meta);
                ff_id3v2_parse_chapters(s, id3v2_extra_meta);
                ff_id3v2_parse_priv(s, id3v2_extra_meta);
            }
            ff_id3v2_free_extra_meta(&id3v2_extra_meta);
            }
            break;
        case MKTAG('c', 'u', 'e', ' '):
            if (size >= 4 && got_fmt && st->codecpar->sample_rate > 0) {
                AVRational tb = {1, st->codecpar->sample_rate};
                unsigned nb_cues = avio_rl32(pb);

                if (size >= nb_cues * 24LL + 4LL) {
                    for (int i = 0; i < nb_cues; i++) {
                        unsigned offset, id = avio_rl32(pb);

                        if (avio_feof(pb))
                            return AVERROR_INVALIDDATA;

                        avio_skip(pb, 16);
                        offset = avio_rl32(pb);

                        if (!avpriv_new_chapter(s, id, tb, offset, AV_NOPTS_VALUE, NULL))
                            return AVERROR(ENOMEM);
                    }
                }
            }
            break;
        }

        /* seek to next tag unless we know that we'll run into EOF */
        if ((avio_size(pb) > 0 && next_tag_ofs >= avio_size(pb)) ||
            wav_seek_tag(wav, pb, next_tag_ofs, SEEK_SET) < 0) {
            break;
        }
    }

break_loop:
    if (!got_fmt && !got_xma2) {
        av_log(s, AV_LOG_ERROR, "no 'fmt ' or 'XMA2' tag found\n");
        return AVERROR_INVALIDDATA;
    }

    if (data_ofs < 0) {
        av_log(s, AV_LOG_ERROR, "no 'data' tag found\n");
        return AVERROR_INVALIDDATA;
    }

    avio_seek(pb, data_ofs, SEEK_SET);

    if (data_size > (INT64_MAX>>3)) {
        av_log(s, AV_LOG_WARNING, "Data size %"PRId64" is too large\n", data_size);
        data_size = 0;
    }

    if (   st->codecpar->bit_rate > 0 && data_size > 0
        && st->codecpar->sample_rate > 0
        && sample_count > 0 && st->codecpar->channels > 1
        && sample_count % st->codecpar->channels == 0) {
        if (fabs(8.0 * data_size * st->codecpar->channels * st->codecpar->sample_rate /
            sample_count /st->codecpar->bit_rate - 1.0) < 0.3)
            sample_count /= st->codecpar->channels;
    }

    if (   data_size > 0 && sample_count && st->codecpar->channels
        && (data_size << 3) / sample_count / st->codecpar->channels > st->codecpar->bits_per_coded_sample  + 1) {
        av_log(s, AV_LOG_WARNING, "ignoring wrong sample_count %"PRId64"\n", sample_count);
        sample_count = 0;
    }

    /* G.729 hack (for Ticket4577)
     * FIXME: Come up with cleaner, more general solution */
    if (st->codecpar->codec_id == AV_CODEC_ID_G729 && sample_count && (data_size << 3) > sample_count) {
        av_log(s, AV_LOG_WARNING, "ignoring wrong sample_count %"PRId64"\n", sample_count);
        sample_count = 0;
    }

    if (!sample_count || av_get_exact_bits_per_sample(st->codecpar->codec_id) > 0)
        if (   st->codecpar->channels
            && data_size
            && av_get_bits_per_sample(st->codecpar->codec_id)
            && wav->data_end <= avio_size(pb))
            sample_count = (data_size << 3)
                                  /
                (st->codecpar->channels * (uint64_t)av_get_bits_per_sample(st->codecpar->codec_id));

    if (sample_count)
        st->duration = sample_count;

    if (st->codecpar->codec_id == AV_CODEC_ID_PCM_S32LE &&
        st->codecpar->block_align == st->codecpar->channels * 4 &&
        st->codecpar->bits_per_coded_sample == 32 &&
        st->codecpar->extradata_size == 2 &&
        AV_RL16(st->codecpar->extradata) == 1) {
        st->codecpar->codec_id = AV_CODEC_ID_PCM_F16LE;
        st->codecpar->bits_per_coded_sample = 16;
    } else if (st->codecpar->codec_id == AV_CODEC_ID_PCM_S24LE &&
               st->codecpar->block_align == st->codecpar->channels * 4 &&
               st->codecpar->bits_per_coded_sample == 24) {
        st->codecpar->codec_id = AV_CODEC_ID_PCM_F24LE;
    } else if (st->codecpar->codec_id == AV_CODEC_ID_XMA1 ||
               st->codecpar->codec_id == AV_CODEC_ID_XMA2) {
        st->codecpar->block_align = 2048;
    } else if (st->codecpar->codec_id == AV_CODEC_ID_ADPCM_MS && st->codecpar->channels > 2 &&
               st->codecpar->block_align < INT_MAX / st->codecpar->channels) {
        st->codecpar->block_align *= st->codecpar->channels;
    }

    ff_metadata_conv_ctx(s, NULL, wav_metadata_conv);
    ff_metadata_conv_ctx(s, NULL, ff_riff_info_conv);

    set_spdif(s, wav);

    return 0;
}

形参s:既是输入型参数也是输出型参数,指向AVFormatContext类型的变量。s->pb包含整个WAV Header的二进制数据。执行wav_read_header函数后,(WAVDemuxContext *)(s->priv_data)->vst->codecpar中的成员变量会被赋值为WAV Header中Format chunk中的信息(包括音频压缩编码格式、采样频率、声道数量、采样位数、码率)。

返回值:返回0表示解码WAV Header成功。返回一个负数表示解码WAV Header失败。

三、wav_read_header函数的内部实现分析

(一)读取WAV Header中的“区块编号”

WAV Header中的第一个区块为“区块编号”,通过下面语句读取“区块编号”的内容(关于avio_rXXX系列函数的用法可以参考:《FFmpeg源码:avio_r8、avio_rl16、avio_rl24、avio_rl32、avio_rl64函数分析》):

    /* read chunk ID */
    tag = avio_rl32(pb);

有的同学看到这里可能会有疑问:根据文章《音视频入门基础:WAV专题(2)——WAV格式简介》的描述,“区块编号”不是大端字节序的吗?而avio_rl32函数是按照小端模式读取四个字节数据。所以为什么是用avio_rl32函数而不是avio_rb32函数读取呢?avio_rb32函数才是按照大端模式读取四个字节数据的吧?

原因是这样的,在语句tag = avio_rl32(pb)之后,又通过MKTAG函数(关于MKTAG和MKBETAG宏定义的用法可以参考:《FFmpeg源码:MKTAG和MKBETAG宏定义分析》)将字符(比如'R', 'I', 'F', 'F')转换为整形,按小端模式存贮,让其跟tag进行比较。所以这就是所谓的“负负得正”,“用avio_rl32函数读取然后跟MKTAG转换出来的整数进行比较”,这个跟“用avio_rb32函数读取然后用MKBETAG转换出来的整数进行比较”,效果是一样的:

    switch (tag) {
    case MKTAG('R', 'I', 'F', 'F'):
        break;
    case MKTAG('R', 'I', 'F', 'X'):
        wav->rifx = 1;
        break;
    case MKTAG('R', 'F', '6', '4'):
        rf64 = 1;
        break;
    case MKTAG('B', 'W', '6', '4'):
        bw64 = 1;
        break;
    default:
        av_log(s, AV_LOG_ERROR, "invalid start code %s in RIFF header\n",
               av_fourcc2str(tag));
        return AVERROR_INVALIDDATA;
    }

所以下面代码块:

    /* read chunk ID */
    tag = avio_rl32(pb);
    switch (tag) {
    case MKTAG('R', 'I', 'F', 'F'):
        break;
    case MKTAG('R', 'I', 'F', 'X'):
        wav->rifx = 1;
        break;
    case MKTAG('R', 'F', '6', '4'):
        rf64 = 1;
        break;
    case MKTAG('B', 'W', '6', '4'):
        bw64 = 1;
        break;
    default:
        av_log(s, AV_LOG_ERROR, "invalid start code %s in RIFF header\n",
               av_fourcc2str(tag));
        return AVERROR_INVALIDDATA;
    }

等价于:

    /* read chunk ID */
    tag = avio_rb32(pb);
    switch (tag) {
    case MKBETAG('R', 'I', 'F', 'F'):
        break;
    case MKBETAG('R', 'I', 'F', 'X'):
        wav->rifx = 1;
        break;
    case MKBETAG('R', 'F', '6', '4'):
        rf64 = 1;
        break;
    case MKBETAG('B', 'W', '6', '4'):
        bw64 = 1;
        break;
    default:
        av_log(s, AV_LOG_ERROR, "invalid start code %s in RIFF header\n",
               av_fourcc2str(tag));
        return AVERROR_INVALIDDATA;
    }

读取到“区块编号”后,如果值等于“RIFF”,表示该文件遵守RIFF格式的规则,按默认处理;如果值等于“RIFX”,让wav->rifx赋值为1,表示该文件遵守RIFX格式的规则;如果值等于“RF64”,让变量rf64赋值为1,表示这是WAVE 64位扩展格式中的一种:WAV RF64;如果值等于“BW64”,让变量bw64赋值为1,表示这是WAVE 64位扩展格式中的一种:WAV BW64;如果值不为上述,打印日志:"invalid start code XXX in RIFF header"并返回AVERROR_INVALIDDATA表示WAV Header中的数据不合法。

(二)读取WAV Header中的“总区块大小”

WAV Header中的第二个区块为“总区块大小”,通过下面语句读取“总区块大小”。由于该值可以由WAV Header中的其它值推导出来,所以对于FFmpeg来讲它没有意义,故FFmpeg没有将其存贮到内部的成员变量中:

    /* read chunk size */
    avio_rl32(pb);

(三)读取WAV Header中的“档案格式”

WAV Header中的第三个区块为“档案格式”。通过下面语句读取该值并进行比较,如果值不为“WAVE”,日志打印"invalid format in RIFF header"表示WAV Header中的数据不合法:

    /* read format */
    if (avio_rl32(pb) != MKTAG('W', 'A', 'V', 'E')) {
        av_log(s, AV_LOG_ERROR, "invalid format in RIFF header\n");
        return AVERROR_INVALIDDATA;
    }

(四)WAVE 64位扩展格式处理

FFmpeg源码内部将WAV和WAVE 64位扩展格式都放到一起处理,下面代码处理文件格式为WAVE 64位时的情况:


    if (rf64 || bw64) {
        if (avio_rl32(pb) != MKTAG('d', 's', '6', '4'))
            return AVERROR_INVALIDDATA;
        size = avio_rl32(pb);
        if (size < 24)
            return AVERROR_INVALIDDATA;
        avio_rl64(pb); /* RIFF size */

        data_size    = avio_rl64(pb);
        sample_count = avio_rl64(pb);

        if (data_size < 0 || sample_count < 0) {
            av_log(s, AV_LOG_ERROR, "negative data_size and/or sample_count in "
                   "ds64: data_size = %"PRId64", sample_count = %"PRId64"\n",
                   data_size, sample_count);
            return AVERROR_INVALIDDATA;
        }
        avio_skip(pb, size - 24); /* skip rest of ds64 chunk */

    }

(五)读取WAV Header中的子区块

WAV Header中包含“Format chunk”、“Data chunk”这种必须存在的子区块,也包含Fact chunk、Cue points chunk、Playlist chunk、Associated data list chunk等可选区块。通过下面语句中的for循环和switch case语句来判断是哪种子区块,然后循环处理:

 for (;;) {
        AVStream *vst;
        size         = next_tag(pb, &tag, wav->rifx);
        next_tag_ofs = avio_tell(pb) + size;

        if (avio_feof(pb))
            break;

        switch (tag) {
        case MKTAG('f', 'm', 't', ' '):
			//...
            break;
        case MKTAG('X', 'M', 'A', '2'):
			//...
            break;
        case MKTAG('d', 'a', 't', 'a'):
			//...
            break;
        case MKTAG('f', 'a', 'c', 't'):
			//...
            break;
        case MKTAG('b', 'e', 'x', 't'):
			//...
            break;
        case MKTAG('S','M','V','0'):
			//...
            goto break_loop;
        case MKTAG('L', 'I', 'S', 'T'):
        case MKTAG('l', 'i', 's', 't'):
			//...
            break;
        case MKTAG('I', 'D', '3', ' '):
        case MKTAG('i', 'd', '3', ' '): {
			//...
            break;
        case MKTAG('c', 'u', 'e', ' '):
			//...
            break;
        }
	}
}

(六)读取WAV Header中“Format chunk”子区块的信息

下面以读取“Format chunk”子区块中的信息为例子进行讲解如何解码子区块。ff_get_wav_header函数中通过wav_parse_fmt_tag函数解析“Format chunk”子区块:

int ff_get_wav_header(AVFormatContext *s, AVIOContext *pb,
                      AVCodecParameters *par, int size, int big_endian)
{
//...
    for (;;) {
    switch (tag) {
        case MKTAG('f', 'm', 't', ' '):
            /* only parse the first 'fmt ' tag found */
            if (!got_xma2 && !got_fmt && (ret = wav_parse_fmt_tag(s, size, st)) < 0) {
                return ret;
            } else if (got_fmt)
                av_log(s, AV_LOG_WARNING, "found more than one 'fmt ' tag\n");

            got_fmt = 1;
            break;
            }
//...
        }
}

而wav_parse_fmt_tag函数内部又调用了ff_get_wav_header函数来获取获取音频压缩编码格式、采样频率、声道数量、采样位数、码率信息(具体可以参考:《音视频入门基础:WAV专题(4)——FFmpeg源码中获取WAV文件音频压缩编码格式、采样频率、声道数量、采样位数、码率的实现》):

static int wav_parse_fmt_tag(AVFormatContext *s, int64_t size, AVStream *st)
{
    AVIOContext *pb = s->pb;
    WAVDemuxContext *wav = s->priv_data;
    int ret;

    /* parse fmt header */
    ret = ff_get_wav_header(s, pb, st->codecpar, size, wav->rifx);
    if (ret < 0)
        return ret;
    handle_stream_probing(st);

    ffstream(st)->need_parsing = AVSTREAM_PARSE_FULL_RAW;

    avpriv_set_pts_info(st, 64, 1, st->codecpar->sample_rate);

    return 0;
}

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

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

相关文章

论文翻译:Large Language Models in Education: Vision and Opportunities

Large Language Models in Education: Vision and Opportunities 文章目录 教育中的大型语言模型&#xff1a;愿景与机遇摘要1 引言2. 教育与LLMsA. 教育背景B. LLMs背景C. 智能教育D. 教育中的LLMs 3. EduLLMs的关键技术4. LLM赋能教育A. LLMs在教育中的应用B. LLMs下教育的特…

免费分享:全国传统村落空间分布数据(附下载方法)

数据简介 本数据是在中国传统村落名录的基础上&#xff0c;通过地理编码&#xff0c;制作成具有空间坐标信息的矢量数据。 数据属性 数据名称&#xff1a;全国传统村落空间分布数据数据时间&#xff1a;2012年至今&#xff0c;更新至第五批空间位置&#xff1a;全国数据格式&…

opencascade AIS_TrihedronOwner源码学习对象的实体所有者用于选择管理

opencascade AIS_TrihedronOwner 前言 AIS_Trihedron对象的实体所有者用于选择管理。 在OpenCascade的AIS&#xff08;交互对象框架&#xff09;中&#xff0c;管理类似AIS_Trihedron的对象的选择涉及理解如何处理实体&#xff08;或所有者&#xff09;以进行选择。 方法 1…

【单片机毕业设计选题24095】-基于手机端的电池电压采集系统

系统功能: 系统上电后&#xff0c;OLED显示三组18650锂电池电压。 第一行显示第一组锂电池电压 第二行显示第二组锂电池电压 第三行显示第三组锂电池电压 第四行显示电压设定阈值 短按B4按键增加电压设定阈值 短按B5按键减小电压设定阈值 如果任意一组电池电压小于电压…

红酒与季节:品味四季的风情

四季轮转&#xff0c;岁月更迭&#xff0c;每个季节都有其不同的韵味与风情。当定制红酒洒派红酒&#xff08;Bold & Generous&#xff09;与四季相遇&#xff0c;它们共同编织出一幅幅美丽的味觉画卷&#xff0c;让我们在品味中感受四季的风情。 一、春之序曲&#xff1a…

【ESP01开发实例】-ESP-01驱动DHT11和DH22传感器

ESP-01驱动DHT11和DH22传感器 文章目录 ESP-01驱动DHT11和DH22传感器1、DHT11/DHT22传感器介绍2、LCD1602介绍3、硬件准备与接线4、代码实现本主题介绍如何使用 DHT11 和 DHT22 相对湿度和温度传感器与 ESP8266 ESP-01 Wi-Fi 模块,将相对湿度和温度的测量值显示在 162 LCD 屏幕…

C++回顾——多态

一、定义 ①从广义上说,多态性是指&#xff1a;一段程序能够处理多种类型对象的能力。在C语言中,这种多态性可以通过包含多态4种形式来实现。强制多态、重载多态、类型参数化多态、包含多态。 ②从实现上来说&#xff0c;多态的分类&#xff1a;静态多态、动态多态。 二、 广…

云原生安全检测工具(容器安全、trivy、veinmind-tools)

目录 trivy 功能 扫描方式&目标 安装 漏洞库下载及更新 漏洞库扫描原理 官方漏洞库生成 使用 扫描镜像 扫描 wazuh-daemonset 所有漏洞 扫描 wazuh-daemonset 高危、严重漏洞(漏洞过滤) 扫描文件系统 Git 存储库漏洞扫描 报告输出格式 veinmind-tools 1.…

Vue的学习(一)

目录 一、Vue的介绍 二、指令 1.v-text 2.v-html 3.v-show 4.v-if 1&#xff09;v-if与v-show的区别 5.v-else 6.v-else-if 7.v-for 1)v-for 与v-if的优先级 8.v-on&#xff1a;click 9.v-bind 10.v-model 三、表单修饰符 1. .lazy 修饰符 2. .number修饰符…

算法通关:014_2:用队列实现栈

文章目录 题目思路问题总结导包有问题&#xff0c;java提供的有关队列的方法不能调用。queue.add()和queue.offer(n)&#xff0c;这两个方法有什么区别什么叫用LinkedList实现队列 代码结果 题目 LeetCode 225. 用队列实现栈 思路 问题总结 导包有问题&#xff0c;java提供的…

银行业数据分析的关键应用场景探索

“ 随着数字经济的蓬勃发展&#xff0c;银行业正经历着前所未有的变革。从传统的存贷款业务到如今的数字化服务&#xff0c;数据分析在银行中扮演着核心角色。本文将探讨银行业数据分析的主要场景&#xff0c;以及选型适配银行业分析需求的数据库技术。” 国内的商业化银行大体…

二维码直达App,Xinstall为你打通运营任督二脉

在移动互联网时代&#xff0c;App的推广和运营显得尤为重要。然而&#xff0c;许多企业在投入大量资源进行App推广和运营时&#xff0c;总会遇到一些棘手的问题&#xff0c;如用户转化率低、数据分析困难等。今天&#xff0c;我们要为大家揭秘一个神奇的助手——Xinstall&#…

RocketMQ5.0 生产者

生产者消息类型&#xff1a; 延迟队列的生产者 package mainimport ("context""fmt""github.com/apache/rocketmq-clients/golang/v5""github.com/apache/rocketmq-clients/golang/v5/credentials"errgroup2 "golang.org/x/sync…

实现函数返回字符的种类

文章目录 一、题目二、思路三、代码实现 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、题目 例如&#xff1a; 分别输入&#xff1a;a B 6分别输出&#xff1a;# * &#xff1f; 二、思路 第一步 获取键盘输入的字符&#xff0c;键盘输入的话…

服务器 Linux 的文件系统初探

好久没更新文章了&#xff0c;最近心血来潮&#xff0c;重新开始知识的累计&#xff0c;做出知识的沉淀~ 万事万物皆文件 文件系统&#xff1a;操作系统如何管理文件&#xff0c;内部定义了一些规则或者定义所以在 Linux 中所有的东西都是以文件的方式进行操作在 Linux 中&am…

Linux中yum、rpm、apt-get、wget的区别,yum、rpm、apt-get常用命令,CentOS、Ubuntu中安装wget

文章目录 一、常见Linux发行版本二、Linux中yum、rpm、apt-get、wget的区别2.1 yum2.2 rpm2.3 apt-get2.4 wget2.5 总结 三、CentOS中yum的作用3.1 yum清空缓存列表3.2 yum显示信息3.3 yum搜索、查看3.4 yum安装3.5 yum删除、卸载程序3.6 yum包的升级、降级 四、Ubuntu中apt-ge…

线段树、贪心与推销员

[NOIP 2015] 推销员 - 洛谷 核心&#xff1a;利用线段树处理贪心内容。建两个线段树维护两端。 #include<bits/stdc.h> using namespace std; int n; int d[100100]; int t[1000100]; int deep;//当前最深 int ans; struct node{int id,mx; }; struct sgt{int a[10001…

8.1-java+tomcat环境的配置+代理

一、回顾 1.安装nodejs&#xff0c;这是一个jdk一样的软件运行环境 yum -y list installed|grep epel yum -y install nodejs node -v 2.下载对应的nodejs软件npm yum -y install npm npm -v npm set config .....淘宝镜像 3.安装vue/cli command line interface 命令行…

技术详解:互联网医院系统源码与医保购药APP的整合开发策略

本篇文章&#xff0c;小编将从系统架构、数据安全、用户体验和技术实现等方面详细探讨互联网医院系统与医保购药APP的整合开发策略。 一、系统架构 1.模块化设计 互联网医院系统与医保购药APP的整合需要采用模块化设计。 2.微服务架构 每个功能模块作为一个独立的微服务&am…