uniapp 使用ffmpeg播放rtsp

uniapp rtsp实时直播视频流 阅读详情
AI摘要

Uniapp正常无法用FFmpeg播放RTSP,需用Android编译原生插件。先使用Android加载FFmpeg对视频解码,再打包代码成so文件,将其用于Uniapp组件打包示例代码,注意包名一致,最后打包成插件供Uniapp使用,还介绍了相关文件路径及编译后文件的使用。

uniapp正常是无法使用ffmpeg进行播放的,我们需要使用android进行编译原生插件。

ffmpeg的版本使用的  7.1

1、首选,需要使用android加载ffmpeg,对视频进行解码等操作

2、其次,对代码进行打包so文件

3、使用so文件在uniapp给的组件打包示例代码中(特别注意,so文件需要包名一致,否则无法使用)

4、打包成插件,让uniapp使用。

android项目结构,如图所示:

需要使用ffmpeg,

CMakeLlists.txt代码:


cmake_minimum_required(VERSION 3.18.1)

project("decodertsp")
include_directories(${CMAKE_SOURCE_DIR}/include/ffmpeg/include/${ANDROID_ABI})
include_directories(${CMAKE_SOURCE_DIR})

set(LIB_DIR ${CMAKE_SOURCE_DIR}/lib/ffmpeg/lib/${ANDROID_ABI})

add_library( # Sets the name of the library.
        decodertsp

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)

add_library(avformat
        SHARED
        IMPORTED)

set_target_properties(
        avformat
        PROPERTIES IMPORTED_LOCATION
        ${LIB_DIR}/libavformat.so)

add_library(avcodec
        SHARED
        IMPORTED)

set_target_properties(
        avcodec
        PROPERTIES IMPORTED_LOCATION
        ${LIB_DIR}/libavcodec.so)

add_library(swscale
        SHARED
        IMPORTED)

set_target_properties(
        swscale
        PROPERTIES IMPORTED_LOCATION
        ${LIB_DIR}/libswscale.so)
add_library(avutil
        SHARED
        IMPORTED)

set_target_properties(
        avutil
        PROPERTIES IMPORTED_LOCATION
        ${LIB_DIR}/libavutil.so)

add_library(swresample
        SHARED
        IMPORTED)

set_target_properties(
        swresample
        PROPERTIES IMPORTED_LOCATION
        ${LIB_DIR}/libswresample.so)

add_library(avfilter
        SHARED
        IMPORTED)

set_target_properties(
        avfilter
        PROPERTIES IMPORTED_LOCATION
        ${LIB_DIR}/libavfilter.so)

#add_library(postproc
#        SHARED
#        IMPORTED)
#
#set_target_properties(
#        postproc
#        PROPERTIES IMPORTED_LOCATION
#        ${LIB_DIR}/libpostproc.so)

find_library( # Sets the name of the path variable.
        log-lib
        log)
target_link_libraries( # Specifies the target library.
        decodertsp
        android
        avcodec
        avfilter
        avformat
        avutil
        swresample
        swscale
#        postproc
        ${log-lib})

native-lib.cpp

#include <jni.h>
#include <string>
#include <include/jniLog.h>
#include "include/Decoder.h"
#include <android/native_window_jni.h>
#include <android/native_window.h>

bool IsStop = false;
extern "C"
JNIEXPORT void JNICALL
Java_rtsp_ffmpeg_player_RtspPlayerView_decodeVideo(JNIEnv *env, jobject thiz, jstring rtsp_url,
                                                      jobject surface) {
    const char *uri = env->GetStringUTFChars(rtsp_url, 0);
    IsStop = false;
    // 解码视频,解码音频类似,解码的流程类似,把之前的代码拷过来
    avformat_network_init();
    AVFormatContext *pFormatContext = NULL;
    int formatOpenInputRes = 0;
    int formatFindStreamInfoRes = 0;
    int audioStramIndex = -1;
    AVCodecParameters *pCodecParameters;
    const AVCodec *pCodec = NULL;
    AVCodecContext *pCodecContext = NULL;
    int codecParametersToContextRes = -1;
    int codecOpenRes = -1;
    int index = 0;
    AVPacket *pPacket = NULL;
    AVFrame *pFrame = NULL;
    formatOpenInputRes = avformat_open_input(&pFormatContext, uri, NULL, NULL);
    const char* version = av_version_info();
    LOGI("Version= %s",version);
    if(formatOpenInputRes<0){
        LOGE("open url error : %s", av_err2str(formatOpenInputRes));
        return;
    }


    formatFindStreamInfoRes = avformat_find_stream_info(pFormatContext, NULL);


    // 查找视频流的 index
    audioStramIndex = av_find_best_stream(pFormatContext, AVMediaType::AVMEDIA_TYPE_VIDEO, -1, -1,
                                          NULL, 0);


    // 查找解码
    pCodecParameters = pFormatContext->streams[audioStramIndex]->codecpar;
    pCodec = avcodec_find_decoder(pCodecParameters->codec_id);

    // 打开解码器
    pCodecContext = avcodec_alloc_context3(pCodec);
    codecParametersToContextRes = avcodec_parameters_to_context(pCodecContext, pCodecParameters);
    codecOpenRes = avcodec_open2(pCodecContext, pCodec, NULL);
    // 1. 获取窗体
    ANativeWindow *pNativeWindow = ANativeWindow_fromSurface(env, surface);
    if(pNativeWindow == NULL){
        //LOGE("获取窗体失败");
        return ;
    }
    // 2. 设置缓存区的数据
    ANativeWindow_setBuffersGeometry(pNativeWindow, pCodecContext->width, pCodecContext->height,WINDOW_FORMAT_RGBA_8888);
    // Window 缓冲区的 Buffer
    ANativeWindow_Buffer outBuffer;
    // 3.初始化转换上下文
    SwsContext *pSwsContext = sws_getContext(pCodecContext->width, pCodecContext->height,
                                             pCodecContext->pix_fmt, pCodecContext->width, pCodecContext->height,
                                             AV_PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL);
    AVFrame *pRgbaFrame = av_frame_alloc();
    int frameSize = av_image_get_buffer_size(AV_PIX_FMT_RGBA, pCodecContext->width,
                                             pCodecContext->height, 1);
    uint8_t *frameBuffer = (uint8_t *) malloc(frameSize);
    av_image_fill_arrays(pRgbaFrame->data, pRgbaFrame->linesize, frameBuffer, AV_PIX_FMT_RGBA,
                         pCodecContext->width, pCodecContext->height, 1);

    pPacket = av_packet_alloc();
    pFrame = av_frame_alloc();
    while (av_read_frame(pFormatContext, pPacket) >= 0 && !IsStop) {
        if (pPacket->stream_index == audioStramIndex) {
            // Packet 包,压缩的数据,解码成 数据
            int codecSendPacketRes = avcodec_send_packet(pCodecContext, pPacket);
            if (codecSendPacketRes == 0) {
                int codecReceiveFrameRes = avcodec_receive_frame(pCodecContext, pFrame);
                if (codecReceiveFrameRes == 0) {
                    // AVPacket -> AVFrame
                    index++;
                    //LOGE("解码第 %d 帧", index);
                    // 假设拿到了转换后的 RGBA 的 data 数据,如何渲染,把数据推到缓冲区
                    sws_scale(pSwsContext, (const uint8_t *const *) pFrame->data, pFrame->linesize,
                              0, pCodecContext->height, pRgbaFrame->data, pRgbaFrame->linesize);
                    // 把数据推到缓冲区
                    if (ANativeWindow_lock(pNativeWindow, &outBuffer, NULL) < 0) {
                        // Handle error
                        LOGE("ANativeWindow_lock is ERROR");
                    }
// Data copy
                    memcpy(outBuffer.bits, frameBuffer, frameSize);
                    if (ANativeWindow_unlockAndPost(pNativeWindow) < 0) {
                        // Handle error
                        LOGE("ANativeWindow_unlockAndPost is ERROR");
                    }
                }
            }
        }
        // 解引用
        av_packet_unref(pPacket);
        av_frame_unref(pFrame);
    }

    // 1. 解引用数据 data , 2. 销毁 pPacket 结构体内存  3. pPacket = NULL
    av_packet_free(&pPacket);
    av_frame_free(&pFrame);

    __av_resources_destroy:
    if (pCodecContext != NULL) {

        avcodec_free_context(&pCodecContext);
        pCodecContext = NULL;
    }

    if (pFormatContext != NULL) {
        avformat_close_input(&pFormatContext);
        avformat_free_context(pFormatContext);
        pFormatContext = NULL;
    }
    avformat_network_deinit();

    env->ReleaseStringUTFChars(rtsp_url, uri);
}
extern "C"
JNIEXPORT void JNICALL
Java_rtsp_ffmpeg_player_RtspPlayerView_stopVideo(JNIEnv *env, jobject thiz) {
    IsStop = true;
}
extern "C"
JNIEXPORT void JNICALL
Java_rtsp_ffmpeg_player_RtspPlayerView_drawToSurface(JNIEnv *env, jclass clazz, jobject surface,
                                                     jint color) {
    ANativeWindow_Buffer nwBuffer;

    LOGI("ANativeWindow_fromSurface ");
    ANativeWindow *mANativeWindow = ANativeWindow_fromSurface(env, surface);

    if (mANativeWindow == NULL) {
        LOGE("ANativeWindow_fromSurface error");
        return;
    }

    LOGI("ANativeWindow_lock ");
    if (0 != ANativeWindow_lock(mANativeWindow, &nwBuffer, 0)) {
        LOGE("ANativeWindow_lock error");
        return;
    }

    LOGI("ANativeWindow_lock nwBuffer->format ");
    if (nwBuffer.format == WINDOW_FORMAT_RGBA_8888) {
        LOGI("nwBuffer->format == WINDOW_FORMAT_RGBA_8888 ");
        for (int i = 0; i < nwBuffer.height * nwBuffer.width; i++) {
            *((int*)nwBuffer.bits + i) = color;
        }
    }
    LOGI("ANativeWindow_unlockAndPost ");
    if (0 != ANativeWindow_unlockAndPost(mANativeWindow)) {
        LOGE("ANativeWindow_unlockAndPost error");
        return;
    }

    ANativeWindow_release(mANativeWindow);

}

RtspPlayerView.java

package rtsp.ffmpeg.player;

import android.content.Context;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class RtspPlayerView  extends SurfaceView implements SurfaceHolder.Callback {

    //加载so库文件
    static {
        System.loadLibrary("decodertsp");
    }


    private  SurfaceHolder holder;
    private  String url = "";

    public  RtspPlayerView(Context context, AttributeSet attrs){
        super(context,attrs);

        init();
    }

    public  RtspPlayerView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        init();
    }

    private  void init(){

        holder = getHolder();
        holder.addCallback(this);
        holder.setFormat(PixelFormat.RGBA_8888);

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Log.i("RtspPlayerView", "Surface create success");


    }
    public  void stop(){
        String bufferedImage = rgb2Hex(0, 0, 0);
        String substring = String.valueOf(bufferedImage).substring(3);
        int color = Integer.parseInt(substring,16);
        drawToSurface(holder.getSurface(),color);
        stopVideo();
    }
    public void play(String uri) {
        this.url = uri;

        if (uri != null && !uri.isEmpty()) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    decodeVideo(url, holder.getSurface());

                }
            }).start();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        Log.i("RtspPlayerView", "Surface ");
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        Log.i("RtspPlayerView", "Surface 销毁");

    }
    public static String  rgb2Hex(int r,int g,int b){
        return String.format("0xFF%02X%02X%02X", r,g,b);
    }

    public static native void drawToSurface(Surface surface, int color);

    private native void decodeVideo(String rtspUrl, Surface surface);
    //private native  void stopVideo();
    //public static native void drawToSurface(Surface surface, int color);
    private native void stopVideo();
}

MainActivity.java

package com.hisign.decodertsp;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Toast;

import com.blankj.utilcode.util.KeyboardUtils;
import com.blankj.utilcode.util.SPUtils;
import com.hisign.decodertsp.databinding.ActivityMainBinding;

import rtsp.ffmpeg.player.RtspPlayerView;

public class MainActivity extends AppCompatActivity {
    private final static String KEY_IP  = "KEY_IP";

    private ActivityMainBinding binding;

    private RtspPlayerView myRtsp;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        String ip = SPUtils.getInstance().getString(KEY_IP);
        binding.etIp.setText(ip);

        myRtsp = new RtspPlayerView(this,null,0);
        binding.rtspParent.addView(myRtsp);



        binding.btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(TextUtils.isEmpty(ip)){
                    Toast.makeText(MainActivity.this, "please input ip!", Toast.LENGTH_SHORT).show();
                    return;
                }

                SPUtils.getInstance().put(KEY_IP,ip);
                String url = "rtsp://admin:123456@"+ip+":554/H.264/ch1/main/av_stream";

                myRtsp.play(url);



                KeyboardUtils.hideSoftInput(MainActivity.this);
            }
        });



        binding.btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myRtsp.stop();
            }
        });
    }


}

app的build.gradle

plugins {
    id 'com.android.application'
}

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.hisign.decodertsp"
        minSdk 23
        targetSdk 32
        versionCode 1
        versionName "1.0"
        ndkVersion "25.1.8937393"  // 指定 NDK 版本
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags '-std=c++11'
            }
        }
        ndk {
            abiFilters 'arm64-v8a','armeabi-v7a'
            //abiFilters 'armeabi-v7a'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'com.blankj:utilcodex:1.31.1'
}

感谢:

原文链接:https://blog.youkuaiyun.com/weixin_46999174/article/details/140531411

编译完成后,会得到so文件  路径:      \app\build\intermediates\merged_native_libs\debug\out\lib

将so文件放在uniapp的示例项目中进行使用  

build.gradle中添加:

android{




   sourceSets{
        main{
            jniLibs.srcDirs = ["libs"]
        }
    }
}

编译完成,

路径:\uniplugin_component\build\outputs\aar  得到aar包

然后在项目中:

这样使用,就没问题了

解决uniapp在安卓app端播放rtsp直播流的播放器问题 阅读详情

相关推荐

保姆级教程:在uni-app中集成FFmpeg 7.1播放RTSP流(Android原生插件实战)

本文提供了一份详细的保姆级教程,指导开发者如何在uni-app中集成FFmpeg 7.1实现RTSP流播放。从FFmpeg编译到Android原生插件开发,再到uni-app插件封装,全面覆盖技术难点,帮助开发者高效解决RTSP流播放问题。

weixin_30572613的博客 189

APP端 uni-app播放rtsp海康监控视频

需求 点击组织之后显示组织下的监控列表,点击监控列表里面的摄像头播放视频 效果图 区域组织列表 点击其中一个区域后跳转监控列表 视频播放页面 前端 区域列表页面 <template> <view style="padding: 10upx 20upx 0upx 20upx;"> <view class="uni-card" v-for="item in list" :key='item.id' @click="showCameraList(item.id)"&gt

爱码代码的小马 1万+

保姆级教程:在UniApp中集成FFmpeg 7.1播放RTSP流(Android原生插件实战)

本教程详细介绍了在UniApp中集成FFmpeg 7.1播放RTSP流的技术方案,重点解决JNI层内存泄漏、多ABI架构兼容性及UniApp与原生模块通信优化等核心问题。通过Android原生插件开发,实现高性能RTSP解码,适用于跨平台流媒体播放需求。

weixin_29168393的博客 222

uniapp 安卓ffmpeg音视频处理原生插件

安卓ffmpeg音视频处理原生插件,支持音视频转换,音视频剪切,添加字幕,添加水印等操作,支持自定义指令,取消指令执行等,功能比较全面,支持RTMP文件推流。

夜中雨滴的专栏 2565

保姆级教程:在UniApp安卓原生插件中集成FFmpeg 7.1播放RTSP流(附完整CMake配置)

本文提供了一份详细的UniApp安卓原生插件开发教程,重点介绍如何集成FFmpeg 7.1实现RTSP流播放功能。从环境准备、CMake配置到JNI层解码核心实现,再到UniApp插件打包与集成,全面解析了开发过程中的关键步骤和常见问题解决方案,帮助开发者高效完成音视频处理功能的开发。

weixin_30952535的博客 430

告别黑屏!UniApp安卓端RTSP直播流播放避坑指南:FFmpeg so库加载与Surface渲染详解

本文深入解析UniApp安卓端RTSP直播流播放的实战技巧,重点解决FFmpeg so库加载与Surface渲染中的常见问题。通过详细的架构设计、ABI兼容性配置和线程安全控制,帮助开发者避免黑屏等典型故障,实现稳定高效的直播流播放功能。

weixin_30300225的博客 227

手机RTSP播放器软件

VLC 手机客户端,用于接受RTSP发送的数据流,方便用户通过手机方式来看视频流

UniApp集成FFmpeg解码RTSP:Android原生插件开发与SO库实战

本文详细介绍了在UniApp中通过开发Android原生插件集成FFmpeg解码RTSP视频流的完整实战方案。针对UniApp自带组件对RTSP支持不佳的问题,文章提供了从编译FFmpeg SO库、编写JNI解码与渲染逻辑,到封装原生视图并打包为UniApp插件的全流程指南,旨在帮助开发者实现跨平台应用中的高性能实时视频播放功能。

weixin_29174385的博客 237

RTSP 流相关工具介绍

RTSP (Real Time Streaming Protocol),实时流协议,是一种应用层协议,专为流媒体使用。本文将介绍 GStreamer, VLC, FFmpeg 这几个工具,如何发送、接收 RTSP 流。 前提 Ubuntu 18.04 LTS (Bionic Beaver) GStreamer 安装: sudo apt install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstream

GoCoding 2411

Unity3D 实现 RTSP 监控视频的低延迟播放方案

本文深入探讨了在Unity3D中实现RTSP监控视频低延迟播放的技术方案。文章分析了RTSP协议的特性与播放难点,对比了商业插件、厂商SDK和基于FFmpeg自主集成三种技术路线的优劣,并重点推荐了基于FFmpeg的自主集成方案。详细阐述了通过FFmpeg拉流解码、环形缓冲区管理及纹理动态更新等核心步骤,实现200毫秒内低延迟播放的完整流程与性能调优技巧。

weixin_29021291的博客 254

uniapp实战:5分钟搞定RTSP直播流播放(附完整组件代码)

本文详细介绍了在UniApp中实现RTSP直播流播放的完整实战方案。针对移动端不支持RTSP协议的核心挑战,文章提出了服务端转码为HLS的解决方案,并提供了封装好的高性能视频播放组件代码,涵盖自动重连、网络自适应、多平台兼容性处理等关键优化点,助力开发者快速实现跨端实时直播功能。

grpc6streamer的博客 911

UniApp安卓原生插件实战:手把手教你用FFmpeg 7.1解码RTSP流(附完整CMake配置)

本文详细介绍了如何在UniApp中开发安卓原生插件,利用FFmpeg 7.1解码RTSP流。从环境搭建、CMake配置到JNI层实现和UniApp插件集成,提供了完整的实战指南,帮助开发者高效实现视频流处理功能。

weixin_30329623的博客 291

大华摄像头视频播放实操:RTSP转HLS全链路与Uniapp播放

网络摄像头视频播放是安防系统接入中的核心环节,H.264/H.265编码的RTSP流虽为标准实时传输协议,却因传输层与封装格式差异无法被浏览器直接播放。通过FFmpeg拉流、转码并借助Nginx等流媒体服务分发为HLS协议,可构建跨平台的无插件播放链路,同时兼顾延迟、清晰度与并发性能。该方案广泛适用于PC Web、Uniapp打包的移动端及监控大屏等场景,尤其适合对接大华等厂商网络摄像头。针对Uniapp视频列表,限制单个视频播放、滑出可视区自动暂停是典型需求;而基于大华SDK的Java Spring Bo

weixin_29611239的博客 335

html5播放rtsp方案

由于工作的原因需要开发海康和大华的摄像头,当中遇到了在浏览器播放实时画面的问题,由于摄像头是rtsp格式的,h5原生不支持这种格式,网上查了很多教程大多是使用rtsp转rtmp,不过rtmp需要falsh的支持,但是在chrome已经默认禁用、包括未来也会逐渐淘汰,所有最终这种方案被淘汰。 于是我想应该把rtsp转化成一种类似http协议的方式,能够直接被h5识别,于是我google,百度就不用...

u014535295的博客 4万+

android rtsp在线视频播放实例

android rtsp在线视频播放实例

vlc-android播放rtsp视频流demo

android集成VLC播放rtsp视频流,很好用,直接传rtsp就可以播放,我也是在网上看到的(之前上传的不知道为什么源码变了,重新上传一次)

Android移植FFmpeg源码实现RTSP流实时播放及保存为MP4文件回放

Android移植FFmpeg源码实现RTSP流实时播放及保存为MP4文件回放 【下载地址】Android移植FFmpeg源码实现RTSP流实时播放及保存为MP4文件回放 该项目提供了一个经过移植的FFmpeg源码,专为Android平台设计,能够实现RTSP流的实时播放,并支持将流保存为MP4文件以便回放。通过实际测试...

gitblog_06789的博客 750

dify案例-基于Dify打造智能合同审查助手:零代码搭建工作流全指南

Dify的工作流(Workflow)功能是一个可视化流程编排工具,允许用户通过拖拽节点的方式构建复杂的AI应用。与简单的问答应用不同,工作流支持多步骤任务处理、条件分支、并行执行等高级功能,非常适合合同审查这类结构化任务。可视化编排:无需编写代码,通过拖拽连接节点即可构建复杂流程多模型协作:可在不同节点使用最适合的AI模型灵活集成:支持知识库检索、条件判断、API调用等多种节点类型迭代优化:可随时测试和调整单个节点,不影响整体流程可视化搭建:无需编码经验,拖拽式构建复杂审查流程灵活扩展。

weixin_44245188的博客 1939
上一篇: 在屏幕中心显示定位点
Little_Code
博客等级 码龄10年 62粉丝 77原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值