Android实现C++通过MediaCodec播放h254的UDP视频流

之前业务需要,在android在播放udp传输的h265视频流,播放效果还行,但延迟不太满意,半秒以上,后面听说用c来解码快一点,就试了一下,结果没卵区别。不过做都做了,把代码写下来记录一下。

首页是界面代码,实现了两个jni接口用于播放和停止播放,同时给surfaceview添加回调:
MainActivity:

class MainActivity : AppCompatActivity(),SurfaceHolder.Callback {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.surfaceview.holder.addCallback(this)
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        Thread {
            startNativePlayback(holder.surface, 50002)
        }.start()
    }
    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
    }

    private external fun startNativePlayback(surface: Surface, port: Int)
    private external fun stopNativePlayback()

    companion object {
        init {
            System.loadLibrary("mediacodecnative")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        stopNativePlayback()
    }
}

界面布局,就一个surfaceview
activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surfaceview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

C++代码,首先是创建udp监听50002端口,接收到数据后发到mediacodec进行解码播放
native-lib.cpp:

#include <jni.h>
#include <string>

#include <jni.h>
#include <android/native_window_jni.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaFormat.h>
#include <pthread.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <android/log.h>
#include <vector>

#define LOG_TAG "###HEVCDecoder"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

struct DecoderContext {
    AMediaCodec *codec = nullptr;
    ANativeWindow *window = nullptr;
    int sockfd = -1;
    bool running = false;
    pthread_t thread;
    int64_t timestampUs = 0;
};

static DecoderContext *gCtx = nullptr;
static void *receiverThread(void *arg);
static void processNalUnit(DecoderContext *ctx, uint8_t *data, size_t size);
static void configureDecoder(DecoderContext *ctx);


extern "C" JNIEXPORT void JNICALL Java_com_test_mediacodecnative_MainActivity_stopNativePlayback(JNIEnv *, jobject);

extern "C" JNIEXPORT void JNICALL
Java_com_test_mediacodecnative_MainActivity_startNativePlayback(
        JNIEnv *env,
        jobject thiz,
        jobject surface,
        jint port) {

    LOGD("startNativePlayback");

    if (gCtx) {
        Java_com_test_mediacodecnative_MainActivity_stopNativePlayback(env, thiz);
    }

    auto *ctx = new DecoderContext();
    gCtx = ctx;

    ctx->window = ANativeWindow_fromSurface(env, surface);

    configureDecoder(ctx);

    // Create UDP socket
    ctx->sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (ctx->sockfd < 0) {
        LOGE("Socket creation failed");
        delete ctx;
        return;
    }

    sockaddr_in server_addr{};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(ctx->sockfd, (sockaddr *) &server_addr, sizeof(server_addr))) {
        LOGE("Bind failed: %s", strerror(errno));
        close(ctx->sockfd);
        delete ctx;
        return;
    }

    // Start receiver thread
    ctx->running = true;
    if (pthread_create(&ctx->thread, nullptr, receiverThread, ctx)) {
        LOGE("Thread creation failed");
        close(ctx->sockfd);
        delete ctx;
    }
}

extern "C" JNIEXPORT void JNICALL
Java_com_test_mediacodecnative_MainActivity_stopNativePlayback(
        JNIEnv *env,
        jobject thiz) {
    if (!gCtx) {
        LOGD("Playback already stopped");
        return;
    }

    // 停止接收线程
    gCtx->running = false;
    pthread_join(gCtx->thread, nullptr);
    LOGD("Receiver thread stopped");

    // 关闭网络套接字
    if (gCtx->sockfd != -1) {
        close(gCtx->sockfd);
        gCtx->sockfd = -1;
        LOGD("Socket closed");
    }

    // 释放解码器资源
    if (gCtx->codec) {
        AMediaCodec_stop(gCtx->codec);
        AMediaCodec_delete(gCtx->codec);
        gCtx->codec = nullptr;
        LOGD("MediaCodec released");
    }

    // 释放NativeWindow
    if (gCtx->window) {
        ANativeWindow_release(gCtx->window);
        gCtx->window = nullptr;
        LOGD("Surface released");
    }

    // 删除上下文对象
    delete gCtx;
    gCtx = nullptr;
    LOGD("Context cleanup completed");
}

static void *receiverThread(void *arg) {
    LOGD("receiverThread");
    auto *ctx = static_cast<DecoderContext *>(arg);
    constexpr int MAX_UDP_SIZE = 1024*10;
    uint8_t buffer[MAX_UDP_SIZE];
    int64_t frameInterval = 1000000 / 30; // 30fps

    while (ctx->running) {
        sockaddr_in client_addr{};
        socklen_t addr_len = sizeof(client_addr);
        ssize_t count = recvfrom(
                ctx->sockfd, buffer, MAX_UDP_SIZE, 0,
                (sockaddr *) &client_addr, &addr_len
        );
        LOGD("receive date count = %d",count);
        if (count > 0) {
            processNalUnit(ctx, buffer, count);
            ctx->timestampUs += frameInterval;
        } else if (count < 0) {
            LOGE("recvfrom error: %s", strerror(errno));
        }
    }

    close(ctx->sockfd);
    ctx->sockfd = -1;
    return nullptr;
}

static std::vector<uint8_t> s_buffer;
static void processNalUnit(DecoderContext *ctx, uint8_t *data, size_t size) {
    if (!ctx->codec) {
        LOGE("codec unknown");
        return;
    }
    uint8_t* data1;
    size_t size1;
    if (size >= 4 && memcmp(data, "\x00\x00\x00\x01", 4) == 0) {//偷懒只判断00000001开头的NAL单元,不是严格判断NAL单元开头
        size1= s_buffer.size();
        data1 = new uint8_t[size1];
        memcpy(data1, s_buffer.data(), size1);
        s_buffer.clear();
        s_buffer.insert(s_buffer.end(), data, data + size);

    }else{
        s_buffer.insert(s_buffer.end(), data, data + size);
        return;
    }

    ssize_t idx = AMediaCodec_dequeueInputBuffer(ctx->codec, 1000);
    if (idx >= 0) {
        size_t bufSize;
        uint8_t *buf = AMediaCodec_getInputBuffer(ctx->codec, idx, &bufSize);
        if (buf && bufSize >= size1) {
            memcpy(buf, data1, size1);
            AMediaCodec_queueInputBuffer(
                    ctx->codec, idx, 0, size1, ctx->timestampUs, 0);
        }
    }

    AMediaCodecBufferInfo info;
    ssize_t outIdx = AMediaCodec_dequeueOutputBuffer(ctx->codec, &info, 1000);
    if (outIdx >= 0) {
        AMediaCodec_releaseOutputBuffer(ctx->codec, outIdx, true);
    }
}

static void configureDecoder(DecoderContext *ctx) {
    int width = 1280, height = 720;

    if (ctx->codec) {
        AMediaCodec_stop(ctx->codec);
        AMediaCodec_delete(ctx->codec);
    }

    ctx->codec = AMediaCodec_createDecoderByType("video/hevc");
    AMediaFormat *format = AMediaFormat_new();
    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "video/hevc");
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, 30);

    if (AMediaCodec_configure(ctx->codec, format, ctx->window, nullptr, 0) != AMEDIA_OK) {
        LOGE("Decoder configuration failed");
    }
    AMediaCodec_start(ctx->codec);
    AMediaFormat_delete(format);
}

CMake配置
CMakeLists.txt:

add_library(${CMAKE_PROJECT_NAME} SHARED
        native-lib.cpp)

target_link_libraries(${CMAKE_PROJECT_NAME}
        android
        mediandk
        log)

别忘了添加网络权限

<uses-permission android:name="android.permission.INTERNET" />

测试视频可以直接用ffmpeg推流电脑摄像头:

ffmpeg -f dshow -i video="USB Video Device" -tune zerolatency -c:v libx265 -pix_fmt yuv420p -preset ultrafast -s 1280x720  -f hevc  udp://xxx.xxx.xxx.xxx:50002
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值