RTMP直播推流(一)视频推流

2023-05-16

关于cmakeList的配置,这里就直接给出代码:

cmake_minimum_required(VERSION 3.4.1)

# 引入指定目录下的CMakeLists.txt
add_subdirectory(src/main/cpp/librtmp)

add_library(
             native-lib

             SHARED

             src/main/cpp/native-lib.cpp
             src/main/cpp/VideoChannel.cpp)

include_directories(src/main/cpp/include)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/cpp/libs/${ANDROID_ABI}")
target_link_libraries(
                       native-lib
                       rtmp
                       x264
            log)

 在视频推流中,java层主要实现初始化、设置画布、转摄像头、开始直播和停止直播操作:

MainActivity:

import androidx.appcompat.app.AppCompatActivity;

import android.hardware.Camera;
import android.os.Bundle;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;

import com.example.rtmp.databinding.ActivityMainBinding;
import com.example.rtmp.live.LivePusher;

public class MainActivity extends AppCompatActivity {

    private LivePusher livePusher;

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

        SurfaceView surfaceView = findViewById(R.id.surfaceView);

        livePusher = new LivePusher(this, 800, 480, 800_000, 10, Camera.CameraInfo.CAMERA_FACING_BACK);
        //  设置摄像头预览的界面
        livePusher.setPreviewDisplay(surfaceView.getHolder());
    }

    public void switchCamera(View view) {
    }

    public void startLive(View view) {
        livePusher.startLive("rtmp://47.75.90.219/myapp/mystream");
    }

    public void stopLive(View view) {
    }
}

LivePush代码:

package com.example.rtmp.live;

import android.app.Activity;
import android.view.SurfaceHolder;

import com.example.rtmp.live.channel.AudioChannel;
import com.example.rtmp.live.channel.VideoChannel;


public class LivePusher {


    static {
        System.loadLibrary("native-lib");
    }

    private AudioChannel audioChannel;
    private VideoChannel videoChannel;

    public LivePusher(Activity activity, int width, int height, int bitrate,
                      int fps, int cameraId) {
        native_init();
        videoChannel = new VideoChannel(this,activity, width, height, bitrate, fps, cameraId);
        audioChannel = new AudioChannel();
    }

    public void setPreviewDisplay(SurfaceHolder surfaceHolder) {
        videoChannel.setPreviewDisplay(surfaceHolder);
    }

    public void switchCamera() {
        videoChannel.switchCamera();
    }

    public void startLive(String path) { //打开编码器之后进行开始直播
        native_start(path); //启动一个线程连接服务器
        videoChannel.startLive();
        audioChannel.startLive();
    }

    public void stopLive(){
        videoChannel.stopLive();
        audioChannel.stopLive();
        native_stop();
    }


    public native void native_init();

    public native void native_start(String path);

    public native void native_setVideoEncInfo(int width, int height, int fps, int bitrate);

    public native void native_pushVideo(byte[] data);

    public native void native_stop();

    public native void native_release();
}

CameraHelper代码:  
package com.example.rtmp.live.channel;

import android.app.Activity;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;

import java.util.Iterator;
import java.util.List;

public class CameraHelper implements SurfaceHolder.Callback, Camera.PreviewCallback {

    private static final String TAG = "CameraHelper";
    private Activity mActivity;
    private int mHeight;
    private int mWidth;
    private int mCameraId;
    private Camera mCamera;
    private byte[] buffer;
    private SurfaceHolder mSurfaceHolder;
    private Camera.PreviewCallback mPreviewCallback;
    private int mRotation;
    private OnChangedSizeListener mOnChangedSizeListener;

    public CameraHelper(Activity activity, int cameraId, int width, int height) {
        mActivity = activity;
        mCameraId = cameraId;
        mWidth = width;
        mHeight = height;
    }

    public void switchCamera() {
        if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        } else {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
        stopPreview();
        startPreview();
    }

    private void stopPreview() {
        if (mCamera != null) {
            //预览数据回调接口
            mCamera.setPreviewCallback(null);
            //停止预览
            mCamera.stopPreview();
            //释放摄像头
            mCamera.release();
            mCamera = null;
        }
    }

    private void startPreview() {
        try {
            //获得camera对象
            mCamera = Camera.open(mCameraId);
            //配置camera的属性
            Camera.Parameters parameters = mCamera.getParameters();
            //设置预览数据格式为nv21
            parameters.setPreviewFormat(ImageFormat.NV21);
            //这是摄像头宽、高
            setPreviewSize(parameters);
            // 设置摄像头 图像传感器的角度、方向
            setPreviewOrientation(parameters);
            mCamera.setParameters(parameters);
            buffer = new byte[mWidth * mHeight * 3 / 2];
            //数据缓存区
            mCamera.addCallbackBuffer(buffer);
            mCamera.setPreviewCallbackWithBuffer(this);
            //设置预览画面
            mCamera.setPreviewDisplay(mSurfaceHolder);
            mOnChangedSizeListener.onChanged(mWidth, mHeight);
            mCamera.startPreview();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void setPreviewOrientation(Camera.Parameters parameters) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        Camera.getCameraInfo(mCameraId, info);
        mRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        int degrees = 0;
        switch (mRotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90: // 横屏 左边是头部(home键在右边)
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:// 横屏 头部在右边
                degrees = 270;
                break;
        }
        int result;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360; // compensate the mirror
        } else { // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        //设置角度
        mCamera.setDisplayOrientation(result);
    }

    private void setPreviewSize(Camera.Parameters parameters) {
        //获取摄像头支持的宽、高
        List<Camera.Size> supportedPreviewSizes = parameters.getSupportedPreviewSizes();
        Camera.Size size = supportedPreviewSizes.get(0);
        Log.d(TAG, "支持 " + size.width + "x" + size.height);
        //选择一个与设置的差距最小的支持分辨率
        // 10x10 20x20 30x30
        // 12x12
        int m = Math.abs(size.height * size.width - mWidth * mHeight);
        supportedPreviewSizes.remove(0);
        Iterator<Camera.Size> iterator = supportedPreviewSizes.iterator();
        //遍历
        while (iterator.hasNext()) {
            Camera.Size next = iterator.next();
            Log.d(TAG, "支持 " + next.width + "x" + next.height);
            int n = Math.abs(next.height * next.width - mWidth * mHeight);
            if (n < m) {
                m = n;
                size = next;
            }
        }
        mWidth = size.width;
        mHeight = size.height;
        parameters.setPreviewSize(mWidth, mHeight);
        Log.d(TAG, "设置预览分辨率 width:" + size.width + " height:" + size.height);
    }


    public void setPreviewDisplay(SurfaceHolder surfaceHolder) {
        mSurfaceHolder = surfaceHolder;
        mSurfaceHolder.addCallback(this);
    }

    public void setPreviewCallback(Camera.PreviewCallback previewCallback) {
        mPreviewCallback = previewCallback;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        //释放摄像头
        stopPreview();
        //开启摄像头
        startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        stopPreview();
    }


    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        // data数据依然是倒的
        mPreviewCallback.onPreviewFrame(data, camera);
        camera.addCallbackBuffer(buffer);
    }



    public void setOnChangedSizeListener(OnChangedSizeListener listener) {
        mOnChangedSizeListener = listener;
    }

    public interface OnChangedSizeListener {
        void onChanged(int w, int h);
    }
}

VideoChannel:

package com.example.rtmp.live.channel;

import android.app.Activity;
import android.hardware.Camera;
import android.view.SurfaceHolder;

import com.example.rtmp.live.LivePusher;


public class VideoChannel implements Camera.PreviewCallback, CameraHelper.OnChangedSizeListener {


    private LivePusher mLivePusher;
    private CameraHelper cameraHelper;
    private int mBitrate;
    private int mFps;
    private boolean isLiving;

    public VideoChannel(LivePusher livePusher, Activity activity, int width, int height, int bitrate, int fps, int cameraId) {
        mLivePusher = livePusher;
        mBitrate = bitrate;
        mFps = fps;
        cameraHelper = new CameraHelper(activity, cameraId, width, height);
        //1、让camerahelper的
        cameraHelper.setPreviewCallback(this);
        //2、回调 真实的摄像头数据宽、高
        cameraHelper.setOnChangedSizeListener(this);
    }

    public void setPreviewDisplay(SurfaceHolder surfaceHolder) {
        cameraHelper.setPreviewDisplay(surfaceHolder);
    }


    /**
     * 得到nv21数据 已经旋转好的
     *
     * @param data
     * @param camera
     */
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (isLiving) {
            mLivePusher.native_pushVideo(data); //将data送到native然后进行编码播放
        }
    }

    public void switchCamera() {
        cameraHelper.switchCamera();
    }

    /**
     * 真实摄像头数据的宽、高
     * @param w
     * @param h
     */
    @Override
    public void onChanged(int w, int h) {
        //初始化编码器
        mLivePusher.native_setVideoEncInfo(w, h, mFps, mBitrate);
    }

    public void startLive() { //跳到onPreviewFrame
        isLiving = true;
    }

    public void stopLive() {
        isLiving = false;
    }
}

可以使用debug看一看操作的流程

下面进行jni层的开发:

首先要初始化设置:

SafeQueue<RTMPPacket*> packets; //打包好的数据
VideoChannel *videoChannel=0;
int isstart=0;
pthread_t pid;

void releasePackets(RTMPPacket*& packet){
    DELETE(packet);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmp_live_LivePusher_native_1init(JNIEnv *env, jobject instance) {
    // 准备一个Video编码器的工具类:进行编码
    videoChannel=new VideoChannel;
    //准备一个队列,打包好的数据 放入队列,在线程中统一的取出数据在再送给服务器
    packets.setReleaseCallback(releasePackets);

}

初始化编码器:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_rtmp_live_LivePusher_native_1setVideoEncInfo(JNIEnv *env, jobject instance, jint width,
                                                              jint height, jint fps, jint bitrate) {
    //
    if (videoChannel){
        videoChannel->setVideoEncInfo(width,height,fps,bitrate);
    }
}

setVideoEncInfo代码如下:

void VideoChannel::setVideoEncInfo(jint width, jint height, jint fps, jint bitrate) {
    pthread_mutex_lock(&mutex);
    mWidtd=width;
    mHeight=height;
    mFps=fps;
    mBitrate=bitrate;
    if (videoCodec){ //进行判断是因为后面进行跳转摄像头需要进行释放之前的摄像头
        x264_encoder_close(videoCodec);
        videoCodec=0;
    }

    //打开x264编码器
    //x264编码器的属性
    x264_param_t param;
    //2:最快
    //3: 无延迟编码
    x264_param_default_preset(&param,"ultrafast","zerolatency");
    //base_line 3.2 编码规格
    param.i_level_idc = 32;
    //输入数据格式
    param.i_csp = X264_CSP_I420;
    param.i_width = width;
    param.i_height = height;
    //无b帧
    param.i_bframe = 0;
    //参数i_rc_method表示码率控制,CQP(恒定质量),CRF(恒定码率),ABR(平均码率)
    param.rc.i_rc_method = X264_RC_ABR;
    //码率(比特率,单位Kbps)
    param.rc.i_bitrate = bitrate / 1000;
    //瞬时最大码率
    param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2;
    //设置了i_vbv_max_bitrate必须设置此参数,码率控制区大小,单位kbps
    param.rc.i_vbv_buffer_size = bitrate / 1000;

    //帧率
    param.i_fps_num = fps;
    param.i_fps_den = 1;
    param.i_timebase_den = param.i_fps_num;
    param.i_timebase_num = param.i_fps_den;
//    param.pf_log = x264_log_default2;
    //用fps而不是时间戳来计算帧间距离
    param.b_vfr_input = 0;
    //帧距离(关键帧)  2s一个关键帧
    param.i_keyint_max = fps * 2;
    // 是否复制sps和pps放在每个关键帧的前面 该参数设置是让每个关键帧(I帧)都附带sps/pps。
    param.b_repeat_headers = 1;
    //多线程
    param.i_threads = 1;

    x264_param_apply_profile(&param,"baseline");
     //打开编码器
    videoCodec= x264_encoder_open(&param);

    pthread_mutex_unlock(&mutex);
}

加一个互斥锁  多线程的时候为了保护线程安全,一般加互斥锁。  

开始直播:

void *start(void *args) {
    char *url = static_cast<char *>(args);
    RTMP *rtmp = 0;
    do {
        rtmp = RTMP_Alloc();
        if (!rtmp) {
            LOGE("alloc rtmp失败");
            break;
        }
        RTMP_Init(rtmp);
        int ret = RTMP_SetupURL(rtmp, url);
        if (!ret) {
            LOGE("设置地址失败:%s", url);
            break;
        }
        //5s超时时间
        rtmp->Link.timeout = 5;
        RTMP_EnableWrite(rtmp);
        ret = RTMP_Connect(rtmp, 0);
        if (!ret) {
            LOGE("连接服务器:%s", url);
            break;
        }
        ret = RTMP_ConnectStream(rtmp, 0);
        if (!ret) {
            LOGE("连接流:%s", url);
            break;
        }
        //记录一个开始时间
        start_time = RTMP_GetTime();
        //表示可以开始推流了
        readyPushing = 1;
        packets.setWork(1);
        RTMPPacket *packet = 0;
        while (readyPushing) {
            packets.pop(packet);
            if (!isStart) {
                break;
            }
            if (!packet) {
                continue;
            }
            packet->m_nInfoField2 = rtmp->m_stream_id;
            //发送rtmp包 1:队列
            // 意外断网?发送失败,rtmpdump 内部会调用RTMP_Close
            // RTMP_Close 又会调用 RTMP_SendPacket
            // RTMP_SendPacket  又会调用 RTMP_Close
            // 将rtmp.c 里面WriteN方法的 Rtmp_Close注释掉
            ret = RTMP_SendPacket(rtmp, packet, 1);
            releasePackets(packet);
            if (!ret) {
                LOGE("发送失败");
                break;
            }
        }
        releasePackets(packet);
    } while (0);
    //
    isStart = 0;
    readyPushing = 0;
    packets.setWork(0);
    packets.clear();
    if (rtmp) {
        RTMP_Close(rtmp);
        RTMP_Free(rtmp);
    }
    delete (url);
    return 0;
}


extern "C"
JNIEXPORT void JNICALL
Java_com_example_push_LivePusher_native_1start(JNIEnv *env, jobject thiz, jstring path_) {
    if (isStart) {
        return;
    }
    const char *path = env->GetStringUTFChars(path_, 0);
    char *url = new char[strlen(path) + 1];
    strcpy(url, path);
    isStart = 1;
    //启动线程
    pthread_create(&pid, 0, start, url);
    env->ReleaseStringUTFChars(path_, path);
}

 编码并发送sps和pps以及I帧:

extern "C"
JNIEXPORT void JNICALL
Java_com_example_push_LivePusher_native_1pushVideo(JNIEnv *env, jobject thiz, jbyteArray data_) {
    if (!videoChannel || !readyPushing) {
        return;
    }
    jbyte *data = env->GetByteArrayElements(data_, NULL);
    videoChannel->encodeData(data);
    env->ReleaseByteArrayElements(data_, data, 0);
}
void VideoChannel::encodeData(int8_t *data) {
    pthread_mutex_lock(&mutex);
    //y数据
    memcpy(pic_in->img.plane[0], data, ySize);
    for (int i = 0; i < uvSize; ++i) {
        //u数据
        *(pic_in->img.plane[1] + i) = *(data + ySize + i * 2 + 1);
        *(pic_in->img.plane[2] + i) = *(data + ySize + i * 2);
    }
    //编码出来的数据  (帧数据)
    x264_nal_t *pp_nal;
    //编码出来有几个数据 (多少帧)
    int pi_nal;
    x264_picture_t pic_out;
    x264_encoder_encode(videoCodec, &pp_nal, &pi_nal, pic_in, &pic_out);
    //如果是关键帧 3
    int sps_len;
    int pps_len;
    uint8_t sps[100];
    uint8_t pps[100];
    for (int i = 0; i < pi_nal; ++i) {
        if (pp_nal[i].i_type == NAL_SPS) {
            //排除掉 h264的间隔 00 00 00 01
            sps_len = pp_nal[i].i_payload - 4;
            memcpy(sps, pp_nal[i].p_payload + 4, sps_len);
        } else if (pp_nal[i].i_type == NAL_PPS) {
            pps_len = pp_nal[i].i_payload - 4;
            memcpy(pps, pp_nal[i].p_payload + 4, pps_len);
            //pps肯定是跟着sps的
            sendSpsPps(sps, pps, sps_len, pps_len);
        } else {
            sendFrame(pp_nal[i].i_type, pp_nal[i].p_payload, pp_nal[i].i_payload);
        }
    }
    pthread_mutex_unlock(&mutex);
}
  • 发送sps 和pps
  • void VideoChannel::sendSpsPps(uint8_t *sps, uint8_t *pps, int sps_len, int pps_len) {
        //看表
        int bodySize = 13 + sps_len + 3 + pps_len;
        RTMPPacket *packet = new RTMPPacket;
        //
        RTMPPacket_Alloc(packet, bodySize);
        int i = 0;
        //固定头
        packet->m_body[i++] = 0x17;
        //类型
        packet->m_body[i++] = 0x00;
        //composition time 0x000000
        packet->m_body[i++] = 0x00;
        packet->m_body[i++] = 0x00;
        packet->m_body[i++] = 0x00;
    
        //版本
        packet->m_body[i++] = 0x01;
        //编码规格
        packet->m_body[i++] = sps[1];
        packet->m_body[i++] = sps[2];
        packet->m_body[i++] = sps[3];
        packet->m_body[i++] = 0xFF;
    
        //整个sps
        packet->m_body[i++] = 0xE1;
        //sps长度
        packet->m_body[i++] = (sps_len >> 8) & 0xff;
        packet->m_body[i++] = sps_len & 0xff;
        memcpy(&packet->m_body[i], sps, sps_len);
        i += sps_len;
    
        //pps
        packet->m_body[i++] = 0x01;
        packet->m_body[i++] = (pps_len >> 8) & 0xff;
        packet->m_body[i++] = (pps_len) & 0xff;
        memcpy(&packet->m_body[i], pps, pps_len);
    
        //视频
        packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
        packet->m_nBodySize = bodySize;
        //随意分配一个管道(尽量避开rtmp.c中使用的)
        packet->m_nChannel = 10;
        //sps pps没有时间戳
        packet->m_nTimeStamp = 0;
        //不使用绝对时间
        packet->m_hasAbsTimestamp = 0;
        packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
    
        videoCallback(packet);
    }

  • 发送帧信息
  • void VideoChannel::sendFrame(int type, uint8_t *payload, int i_payload) {
        if (payload[2] == 0x00) {
            i_payload -= 4;
            payload += 4;
        } else {
            i_payload -= 3;
            payload += 3;
        }
        //看表
        int bodySize = 9 + i_payload;
        RTMPPacket *packet = new RTMPPacket;
        //
        RTMPPacket_Alloc(packet, bodySize);
    
        packet->m_body[0] = 0x27;
        if(type == NAL_SLICE_IDR){
            packet->m_body[0] = 0x17;
            LOGE("关键帧");
        }
        //类型
        packet->m_body[1] = 0x01;
        //时间戳
        packet->m_body[2] = 0x00;
        packet->m_body[3] = 0x00;
        packet->m_body[4] = 0x00;
        //数据长度 int 4个字节
        packet->m_body[5] = (i_payload >> 24) & 0xff;
        packet->m_body[6] = (i_payload >> 16) & 0xff;
        packet->m_body[7] = (i_payload >> 8) & 0xff;
        packet->m_body[8] = (i_payload) & 0xff;
    
        //图片数据
        memcpy(&packet->m_body[9], payload, i_payload);
    
        packet->m_hasAbsTimestamp = 0;
        packet->m_nBodySize = bodySize;
        packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;
        packet->m_nChannel = 0x10;
        packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
        videoCallback(packet);
    }

     

     

  • 链接:https://pan.baidu.com/s/101sPDxMVJd9XeC7JWsmANw 
    提取码:bra3 
     

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RTMP直播推流(一)视频推流 的相关文章

  • scp 远程复制命令介绍

    scp r 复制文件 scp P xff1a 复制指定端口号 目标 主机A 文件复制到主机B某指定目录下 实例 xff1a 将服务器 192 168 2 101 中文件夹 home bd 复制到 192 168 2 77 的目录 home
  • Django 项目迁移

    Django 项目APP Initapp 更新数据库 PS D Work Git Init Web Risk Init Init Sys gt PS D Work Git Init Web Risk Init Init Sys gt pyt
  • Xmind 转 Excel or CSV 格式的TestCase

    Xmind 脑图转 TestCase 随笔记录 1 新建Python 项目 Open Pycharm gt File gt New Project 2 下载安装包 xmind2testcase 和xmind2testlink File gt
  • 服务器蓝屏怎么回事,怎么解决?

    最近有小伙伴和我表示 xff0c 打开服务器是遇到蓝屏了 xff0c 有点慌 xff0c 咨询我有没有什么解决办法 xff0c 今天我在这边总结一下 一 服务器蓝屏原因 xff1a 1 版本冲突 2 软硬件不兼容 3 应用程序存在着BUG
  • zookeeper 集群搭建

    准备环境修改hostname 永久修改hostname root 64 bogon java hostnamectl set hostname server 247 root 64 bogon java root 64 bogon java
  • 为什么更多APP开发者选择穿山甲作为游戏变现平台?

    当前手游行业发展迅速 xff0c 游戏APP用户存量稳定 xff0c 变现价值大 而在选择游戏变现平台时 xff0c 更多开发者青睐于穿山甲平台 穿山甲平台有何优势 为什么会受到这么多APP开发者的信赖呢 穿山甲是国内领先的第三方变现平台
  • 穿山甲平台助力开发者降本增效,技术进阶

    在存量市场 xff0c 变现是开发者的头等大事 xff0c 开发者想要冲破重围 xff0c 必须要探索自我商业化道路与模式 广告变现是当下众多开发者的选择 xff0c 广告变现的路径有两条 xff1a 一 xff0c 保证广告位的填充率 二
  • Debian安装JDK-17.0.5教程

    第一步 xff1a 创建一个java文件夹 mkdir java 第二步 xff1a 打开java文件夹 cd java 第三步 xff1a 下载Linux版本的JDK xff08 jdk 17 linux x64 bin tar gz必须
  • STM32 控制LED灯 亮灭

    lcd c include 34 led h 34 void Delay uint32 t count unsigned int i for count 61 0 count i 61 500 while i void LED GPIO C
  • 树莓派import cv2 失败解决方法

    设备 树莓派4b 问题简述 xff1a 原装系统自带python3 9 2 xff0c 参考了大佬流 浪 猫的教程 超简单教你在树莓派上安装opencv xff08 二 xff09 时 xff0c 遇到了一个依赖源的安装错误 xff0c 直
  • Ansible学习笔记

    目录 1 Ansible搭建 xff08 基于CentOS 7 9 xff09 1 1 在控制节点和被控节点获取epel源 1 2 安装Ansible 2 理论 3 基础配置 3 1 Ansible发送指令的原理 3 2 Ansible配置
  • 半字符入栈的回文判定

    回文是指正读反读均相同的字符序列 xff1b 如 abba 和 abdba 均是回文 xff0c 但 good 不是回文 试写一个算法判定给定的字符序列是否是回文 xff08 提示 xff1a 将一半字符入栈 xff09 算法分析 xff1
  • vue中封装axios 以及api 统一管理

    目录 一 安装axios二 创建文件夹三 封装axios1 引入axios2 引入vuex3 引入vant4 引入我们的环境变量5 创建axios实例6 添加请求拦截7 响应拦截8 导出封装的axios函数 四 api统一管理1 引入我们封
  • 提示虚拟内存不足可能的原因以及该怎么处理

    一 什么是虚拟内存 虚拟内存的概念是相对于物理内存而言的 在硬盘上开辟一块磁盘空间当做内存使用 xff0c 这部分硬盘空间就叫虚拟内存 xff0c 是Windows系统将部分硬盘空间作为内存来使用 xff0c 解决物理内存被占用过多 xff
  • vue常用指令

    常用指令 xff1a vue常用指令有 xff1a v html指令 v show指令 v if指令 v else指令 v else if指令 v for指令 v text指令 v bind指令 v on指令 v model指令等等 1 v
  • wsl的安装及迁移至其他盘

    安装 xff1a wsl是什么 xff1a 适用于Linux的Windows子系统 编辑 维基百科 xff0c 自由的百科全书 xff08 重定向自Windows Subsystem for Linux xff09 跳到导航跳到搜索 适用于
  • ubuntu 不能检测到显卡信息

    开始时 xff1a lspci grep 39 VGA 39 lspci grep 39 VGA 39 01 00 0 VGA compatible controller NVIDIA Corporation Device 2560 rev
  • 记录Vue项目中使用的各插件

    记录项目 package json 文件中各插件的使用 xff1a 目录 1 axios 2 babel polyfill 3 core js 4 echarts 5 element ui 6 es6 promise 7 js beauti
  • 看懂机器视觉(CV)\听懂语音识别(ASR)\理解自然语言处理(NLP)

    缩略语 机器视觉 xff08 CV xff09 Computer vision 语音识别 xff08 ASR xff09 Automatic Speech Recognition 自然语言处理 xff08 NLP xff09 Natural
  • zeal 文档下载及其离线文档下载

    下载地址 百度网盘 链接 xff1a https pan baidu com s 1r4d1OfrUTrbaZ9k9YfdWLA pwd 61 o7au 提取码 xff1a o7au 下载讲解 xff1a 1 下载安装 2 Tools gt

随机推荐

  • Linux系统部署JAVA项目

    Linux系统部署项目 一 准备工作 1 WinSCP 远程服务器可视化工具 2 Xshell5 linux服务器打命令用的 3 准备一台阿里云 华为云 腾讯云服务器 要求 xff1a 系统镜像centeOS7 0以上 二 开始搭建环境 1
  • Spring Boot 之---什么是热部署?---怎么使用?

    Spring Boot 之 什么是热部署 xff1f 怎么使用 xff1f 1 什么是热部署2 怎么启用热部署 xff08 1 xff09 手动配置 xff08 2 xff09 自动配置 总结 1 什么是热部署 所谓热部署 xff0c 就是
  • 什么是反向代理?

    正向代理 当客户端访问一台服务器有障碍 xff0c 访问不到的时候 xff0c 这时候就可以找一台可以访问到该服务器的另外一台服务器去代替他去访问 xff0c 这台代替他去访问的服务器称之为代理服务器 然后客户端就可以把请求发送给代理服务器
  • Java IO流 实现文件复制

    Java IO流 实现文件复制 思路 1 将指定文件转换成输入流 2 创建数组接收输入字节 3 将文件复制想要存放的轮径创建输出流 4 将输入流的字节输出出去 实现 span class token comment 创建输入和输出流 spa
  • Python之文件读写

    1 写文件 f 61 open 39 out txt 39 39 w 39 f write 39 s d d d d 0 0 0 0 0 0 0 39 bbx name bbx x bbx y bbx w bbx h f close 2 读
  • Java 基于TCP的socket实现文件传输

    Java 基于TCP的socket实现文件传输 基于TCP的socket结合java的io流 实现客户端与服务器之间的文件传输 Socket 套接字 xff08 socket xff09 是一个抽象层 xff0c 应用程序可以通过它发送或接
  • MySQL索引的创建与使用

    索引的分类 在学习如何创建索引之前 xff0c 先了解一下索引的分类 MySQL中分为 xff1a 普通索引 xff0c 唯一索引 xff0c 主键索引 xff0c 组合索引 xff0c 和全文索引 index name xff1a 索引名
  • ThreadLocal类

    ThreadLocal类 什么是ThreadLocal为什么ThreadLocal是线程安全的呢 什么是ThreadLocal ThreadLocal可以简单的理解为他其实就是一个工具类 xff0c 用来存储线程局部变量的一个工具类 xff
  • spring boot 访问HTML

    HTML整合spring boot 简介默认文件路径访问自定义文件路径访问 或通过Controller控制器层跳转访问 简介 SpringBoot默认的页面映射路径 xff08 即模板文件存放的位置 xff09 为 classpath te
  • HTML重定向解析ModelMap

    HTML实现重定向解析ModelMap 日常开发中 很多场景需要跳转页面 xff0c 又要携带参数 xff0c 重定向就可以起到很好的作用 业务场景 xff1a 登录成功后展示用户信息 登录页面输入用户名 密码进行登录访问 span cla
  • RHCE-ansible(一)--- 安装ansible、主机清单、sudo提权、特权升级

    目录 一 环境配置 1 配置三个主机 etc hosts 文件 xff0c 实现通过域名访问 2 配置SSH远程免密连接 2 1 在控制主机生成密钥 2 2 发送公钥到受控主机 二 受控主机 xff08 xixi xff09 安装ansib
  • 针对opencv导入Android studio不成功的解决办法?

    一 问题如下 xff1a AS gt File gt New gt Import Module 选择导入 压缩包路径 sdk java文件夹 xff0c 然后发现AS没有下一步 xff1f 二 解决办法 新建一个项目 在新建项目下创建一个包
  • Attempt to invoke virtual method ‘void android.widget.TextView.setText(java.lang.CharSequence)‘ on a

    问题简述 xff1a Attempt to invoke virtual method 39 void android widget TextView setText java lang CharSequence 39 on a null
  • WebRTC使用Linux搭建服务器(二)

    搭建服务器流程 xff1a 注意 xff1a 每个人搭建服务器可能会出现奇奇怪怪的问题 xff0c 照着我的方法可能会出现其他问题 xff0c 不要着急 xff0c 耐心搭建 xff0c 确实比较烦 1 安装JDK apt get upda
  • Java基础——有无参数和有无返回值

    一 有无参数 有参数 xff1a 小括号里面有内容 xff0c 当一个方法需要一些数据条件 xff0c 才能完成任务的时候 xff0c 就是有参数 例如两个数字相加 xff0c 必须知道两个数字各自有多少 xff0c 才能相加 无参数 xf
  • STM32学习:利用寄存器点亮LED

    使用普中PZ6806L开发板 由对应的LED模块的电路可知 xff0c 要想点亮一个LED xff0c 就要将其对应的引脚输出低电平 要使用寄存器 xff0c 首先要对其进行封装 xff0c 具体代码如下 xff1a define PERI
  • java基础——求数组长度、遍历数组、求最值和数组元素反转

    一 求数组长度 获取数组的长度的格式 xff1a 数组名称 length 这将会得到一个int数字 xff0c 代表数组的长度 数组一旦创建 xff0c 程序运行期间 xff0c 长度不可改变 代码如下 xff1a public class
  • java基础—Random

    一 概述 Random 类用来生成随机数字 xff0c xff0c 使用起来也是三个步骤 xff1a 1 导包 2 创建 Random r 61 new Random 小括号留空即可 3 使用 获取一个随机数的int的数字 范围是int所有
  • ffmpeg播放器(二)音频解码与播放

    音频解码和播放的前面准备工作和视频的格式差不多 xff0c 创建两个线程分别解码和播放 xff0c 这里统一只放代码了 void AudioChannel play 设置为播放状态 packets setWork 1 frames setW
  • RTMP直播推流(一)视频推流

    关于cmakeList的配置 xff0c 这里就直接给出代码 xff1a cmake minimum required VERSION 3 4 1 引入指定目录下的CMakeLists txt add subdirectory src ma