Initial commit

This commit is contained in:
2026-05-08 11:45:15 +02:00
commit e5471b5fa0
120 changed files with 8938 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
build/
+57
View File
@@ -0,0 +1,57 @@
cmake_minimum_required(VERSION 3.22.1)
project(camera_hook LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# Find Android libraries
find_library(log-lib log)
find_library(dl-lib dl)
find_library(mediandk-lib mediandk)
find_library(android-lib android)
# HAL hook shared library
add_library(camera_hook SHARED
src/camera_wrapper.cpp
src/video_decoder.cpp
src/rtsp_client.cpp
src/buffer_converter.cpp
)
target_include_directories(camera_hook PRIVATE
${CMAKE_SOURCE_DIR}/include
)
target_link_libraries(camera_hook
${log-lib}
${dl-lib}
${mediandk-lib}
${android-lib}
)
# Compiler flags for LD_PRELOAD hook
target_compile_options(camera_hook PRIVATE
-Wall
-Wextra
-fvisibility=hidden
-fPIC
)
# Enable NEON intrinsics for ARM64
if(ANDROID_ABI STREQUAL "arm64-v8a")
target_compile_options(camera_hook PRIVATE -march=armv8-a+simd)
endif()
# Output name
set_target_properties(camera_hook PROPERTIES
OUTPUT_NAME "camera_hook"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../app/src/main/jniLibs/${ANDROID_ABI}"
)
# Camera provider wrapper (static C binary, bind-mounted over original)
add_executable(camera_provider_wrapper src/camera_provider_wrapper.c)
target_link_options(camera_provider_wrapper PRIVATE -static)
set_target_properties(camera_provider_wrapper PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/../root-module"
)
+72
View File
@@ -0,0 +1,72 @@
#ifndef BUFFER_CONVERTER_H
#define BUFFER_CONVERTER_H
#include <cstdint>
#include <cstddef>
// Buffer format conversion utilities for HAL compatibility.
// Converts between decoder output formats and HAL-expected formats.
// Uses ARM NEON intrinsics for performance on Pixel 9a (arm64-v8a).
namespace buffer_converter {
// Convert YUV420 planar (I420) to NV21 (semi-planar, VU interleaved)
// YUV420 planar: Y plane, U plane, V plane (separate)
// NV21: Y plane, VU interleaved plane
//
// src_y, src_u, src_v: source plane pointers
// src_y_stride, src_uv_stride: source plane strides (bytes per row)
// dst_nv21: destination buffer (Y plane followed by VU interleaved)
// dst_y_stride, dst_uv_stride: destination strides
// width, height: frame dimensions
//
// Returns true on success, false on error
bool yuv420_planar_to_nv21(
const uint8_t* src_y, int src_y_stride,
const uint8_t* src_u, int src_u_stride,
const uint8_t* src_v, int src_v_stride,
uint8_t* dst_nv21, int dst_y_stride, int dst_uv_stride,
int width, int height);
// Convert YUV420 planar (I420) to YUV420 planar with different stride
// Handles stride mismatch between decoder output and HAL buffer.
// Copies Y plane with stride adjustment, then U and V planes.
//
// Returns true on success
bool yuv420_planar_copy_with_stride(
const uint8_t* src_y, int src_y_stride,
const uint8_t* src_u, int src_u_stride,
const uint8_t* src_v, int src_v_stride,
uint8_t* dst_y, int dst_y_stride,
uint8_t* dst_u, int dst_u_stride,
uint8_t* dst_v, int dst_v_stride,
int width, int height);
// Convert NV21 (semi-planar) to YUV420 planar (I420)
// Used when decoder outputs NV21 but HAL expects planar.
//
// Returns true on success
bool nv21_to_yuv420_planar(
const uint8_t* src_nv21, int src_y_stride, int src_uv_stride,
uint8_t* dst_y, int dst_y_stride,
uint8_t* dst_u, int dst_u_stride,
uint8_t* dst_v, int dst_v_stride,
int width, int height);
// Fast Y plane copy with NEON (handles stride mismatch)
// Copies width bytes per row, advancing by stride each row.
void copy_plane_neon(
const uint8_t* src, int src_stride,
uint8_t* dst, int dst_stride,
int width, int height);
// Interleave U and V planes into NV21 format (VU order) using NEON
void interleave_uv_to_nv21_neon(
const uint8_t* src_u, int src_u_stride,
const uint8_t* src_v, int src_v_stride,
uint8_t* dst_vu, int dst_vu_stride,
int width, int height);
} // namespace buffer_converter
#endif // BUFFER_CONVERTER_H
+34
View File
@@ -0,0 +1,34 @@
#ifndef CAMERA_HAL_ICAMERA_DEVICE_H
#define CAMERA_HAL_ICAMERA_DEVICE_H
#include "types.h"
#include <cstdint>
namespace camera_hal {
// ICameraDevice AIDL v4 interface
// Mirrors: hardware/interfaces/camera/device/aidl/android/hardware/camera/device/ICameraDevice.aidl
class ICameraDevice {
public:
virtual ~ICameraDevice() = default;
virtual int32_t open(void* callback) = 0;
virtual int32_t openInjectionSession(void* callback) = 0;
virtual int32_t setTorchMode(bool enabled) = 0;
virtual int32_t dumpState(int32_t fd) = 0;
virtual int32_t getCameraCharacteristics(void* characteristics) = 0;
virtual int32_t getPhysicalCameraCharacteristics(
const char* physical_camera_id,
void* characteristics) = 0;
virtual int32_t close() = 0;
};
// Function pointer types for dlsym
typedef int32_t (*ICameraDevice_open_t)(void* self, void* callback);
typedef int32_t (*ICameraDevice_close_t)(void* self);
typedef int32_t (*ICameraDevice_getCameraCharacteristics_t)(
void* self, void* characteristics);
} // namespace camera_hal
#endif // CAMERA_HAL_ICAMERA_DEVICE_H
@@ -0,0 +1,60 @@
#ifndef CAMERA_HAL_ICAMERA_DEVICE_SESSION_H
#define CAMERA_HAL_ICAMERA_DEVICE_SESSION_H
#include "types.h"
#include <cstdint>
namespace camera_hal {
// ICameraDeviceSession AIDL v4 interface
// Mirrors: hardware/interfaces/camera/device/aidl/android/hardware/camera/device/ICameraDeviceSession.aidl
class ICameraDeviceSession {
public:
virtual ~ICameraDeviceSession() = default;
virtual int32_t configureStreams(
const Stream* streams,
int32_t stream_count,
StreamConfigurationMode mode,
HalStreamConfiguration* out_config) = 0;
virtual int32_t processCaptureRequest(
const CaptureRequest* requests,
int32_t request_count,
int32_t* out_num_request_processed) = 0;
virtual int32_t flush() = 0;
virtual int32_t close() = 0;
virtual int32_t signalStreamFlush(const int32_t* stream_ids, int32_t count) = 0;
virtual int32_t getCaptureRequestMetadataQueue(void* queue) = 0;
virtual int32_t getCaptureResultMetadataQueue(void* queue) = 0;
virtual int32_t switchToOffline(
const int32_t* streams_to_keep,
int32_t count,
void* out_offline_session) = 0;
virtual int32_t isReconfigurationRequired(
const void* old_session_params,
const void* new_session_params,
bool* out_required) = 0;
};
// Function pointer types for dlsym
typedef int32_t (*ICameraDeviceSession_configureStreams_t)(
void* self,
const Stream* streams,
int32_t stream_count,
StreamConfigurationMode mode,
HalStreamConfiguration* out_config);
typedef int32_t (*ICameraDeviceSession_processCaptureRequest_t)(
void* self,
const CaptureRequest* requests,
int32_t request_count,
int32_t* out_num_request_processed);
typedef int32_t (*ICameraDeviceSession_flush_t)(void* self);
typedef int32_t (*ICameraDeviceSession_close_t)(void* self);
} // namespace camera_hal
#endif // CAMERA_HAL_ICAMERA_DEVICE_SESSION_H
@@ -0,0 +1,42 @@
#ifndef CAMERA_HAL_ICAMERA_PROVIDER_H
#define CAMERA_HAL_ICAMERA_PROVIDER_H
#include "types.h"
#include <cstdint>
namespace camera_hal {
// ICameraProvider AIDL v3 interface
// Mirrors: hardware/interfaces/camera/provider/aidl/android/hardware/camera/provider/ICameraProvider.aidl
class ICameraProvider {
public:
virtual ~ICameraProvider() = default;
virtual int32_t setCallback(void* callback) = 0;
virtual int32_t getCameraIdList(char*** camera_ids, int32_t* count) = 0;
virtual int32_t getCameraDeviceInterface(
const char* camera_id,
void** out_device) = 0;
virtual int32_t notifyDeviceStateChange(int64_t device_state) = 0;
virtual int32_t getConcurrentStreamingCameraIds(
char*** camera_ids, int32_t* count) = 0;
virtual int32_t openSession(
const char* camera_id,
void* callback,
void** out_session) = 0;
virtual int32_t getVendorTags(void* tags) = 0;
virtual int32_t getCameraCharacteristics(
const char* camera_id,
void* characteristics) = 0;
};
typedef int32_t (*ICameraProvider_getCameraIdList_t)(
void* self, char*** camera_ids, int32_t* count);
typedef int32_t (*ICameraProvider_getCameraDeviceInterface_t)(
void* self, const char* camera_id, void** out_device);
typedef int32_t (*ICameraProvider_openSession_t)(
void* self, const char* camera_id, void* callback, void** out_session);
} // namespace camera_hal
#endif // CAMERA_HAL_ICAMERA_PROVIDER_H
+103
View File
@@ -0,0 +1,103 @@
#ifndef CAMERA_HAL_TYPES_H
#define CAMERA_HAL_TYPES_H
#include <cstdint>
#include <cstddef>
// AIDL Camera HAL v4 common types
// These mirror the AIDL interface types from hardware/interfaces/camera/
namespace camera_hal {
// Stream format constants (matching AIDL CameraMetadata)
enum StreamFormat : int32_t {
FORMAT_YUV_420_888 = 32,
FORMAT_IMPLEMENTATION_DEFINED = 35,
FORMAT_BLOB_JPEG = 37,
FORMAT_RAW16 = 38,
FORMAT_RAW_PRIVATE = 39,
FORMAT_RAW10 = 40,
FORMAT_RAW12 = 41,
FORMAT_DEPTH16 = 42,
FORMAT_DEPTH_POINT_CLOUD = 43,
FORMAT_PRIVATE = 50,
};
// Stream direction
enum StreamDirection : int32_t {
STREAM_OUTPUT = 0,
STREAM_INPUT = 1,
};
// Stream configuration
struct Stream {
int32_t id;
int32_t width;
int32_t height;
StreamFormat format;
StreamDirection direction;
int32_t usage;
int32_t rotation;
int32_t data_space;
void* physical_camera_id; // nullable
};
// Buffer status
enum BufferStatus : int32_t {
BUFFER_STATUS_OK = 0,
BUFFER_STATUS_ERROR = 1,
BUFFER_STATUS_NO_BUFFER = 2,
};
// Camera buffer descriptor (wraps AHardwareBuffer / GraphicBuffer)
struct CameraBuffer {
int32_t stream_id;
int64_t buffer_id;
void* handle; // AHardwareBuffer* or native_handle_t*
int32_t status;
int64_t timestamp;
void* acquire_fence;
void* release_fence;
};
// Capture request
struct CaptureRequest {
int32_t frame_number;
int32_t settings_count;
void* settings; // CameraMetadata*
int32_t input_buffer_present;
CameraBuffer* input_buffer;
int32_t output_buffer_count;
CameraBuffer* output_buffers;
int32_t physical_camera_id_count;
void* physical_camera_ids;
void* physical_camera_settings;
};
// Capture result
struct CaptureResult {
int32_t frame_number;
void* result; // CameraMetadata*
int32_t output_buffer_count;
CameraBuffer* output_buffers;
int32_t physical_camera_metadata_count;
void* physical_camera_ids;
void* physical_camera_metadata;
};
// Stream configuration mode
enum StreamConfigurationMode : int32_t {
NORMAL_MODE = 0,
CONSTRAINED_HIGH_SPEED_MODE = 1,
};
// HalStreamConfiguration (result of configureStreams)
struct HalStreamConfiguration {
int32_t stream_count;
Stream* streams;
StreamConfigurationMode mode;
};
} // namespace camera_hal
#endif // CAMERA_HAL_TYPES_H
+59
View File
@@ -0,0 +1,59 @@
#ifndef RTSP_CLIENT_H
#define RTSP_CLIENT_H
#include <cstdint>
#include <cstddef>
#include <string>
namespace rtsp_client {
static constexpr int RTSP_DEFAULT_PORT = 554;
static constexpr int RTP_BUFFER_SIZE = 65536;
static constexpr int MAX_URL_LEN = 512;
struct RtspState {
std::string url;
std::string host;
int port;
std::string path;
int rtsp_fd;
int rtp_fd;
int rtcp_fd;
int local_rtp_port;
int local_rtcp_port;
std::string session_id;
std::string control_url;
std::string video_track_url;
bool connected;
bool running;
bool playing;
pthread_t receiver_thread;
int shmem_fd;
uint8_t* shmem_base;
size_t shmem_size;
int32_t width;
int32_t height;
uint32_t frame_count;
uint64_t bytes_received;
};
int init_rtsp(const char* url);
int connect_rtsp();
int play_rtsp();
void stop_rtsp();
void release_rtsp();
bool is_rtsp_connected();
int get_rtsp_shmem_fd();
int get_rtsp_width();
int get_rtsp_height();
} // namespace rtsp_client
#endif // RTSP_CLIENT_H
+100
View File
@@ -0,0 +1,100 @@
#ifndef VIDEO_DECODER_H
#define VIDEO_DECODER_H
#include <cstdint>
#include <cstddef>
#include <string>
// Video decoder using NDK MediaCodec API
// Decodes video files to YUV420 frames and writes to shared memory ring buffer
// for consumption by the HAL hook in the camera provider process.
namespace video_decoder {
// Shared memory ring buffer header
// This structure is placed at the beginning of the memfd shared memory region.
struct RingBufferHeader {
uint32_t magic; // 0xCAM2MAGC
uint32_t version; // Header version (1)
uint32_t frame_count; // Number of frame slots in ring buffer
uint32_t frame_width; // Decoded frame width
uint32_t frame_height; // Decoded frame height
uint32_t frame_size; // Size of one YUV420 frame (w * h * 3/2)
uint32_t write_index; // Atomic: next slot to write
uint32_t read_index; // Atomic: last slot read by consumer
uint32_t sequence; // Monotonically increasing frame counter
uint32_t flags; // Bit flags (bit 0: decoder running)
uint64_t last_timestamp; // Timestamp of last written frame (us)
uint8_t reserved[216]; // Padding to 256 bytes
};
static constexpr uint32_t RING_MAGIC = 0xCA22A61C;
static constexpr uint32_t RING_VERSION = 1;
static constexpr uint32_t RING_FRAME_COUNT = 4;
static constexpr uint32_t RING_HEADER_SIZE = 256;
static constexpr uint32_t FLAG_DECODER_RUNNING = 0x1;
// Calculate total shared memory size needed
inline size_t calc_shmem_size(uint32_t width, uint32_t height, uint32_t frame_count = RING_FRAME_COUNT) {
size_t frame_size = (size_t)width * height * 3 / 2; // YUV420
return RING_HEADER_SIZE + (frame_size * frame_count);
}
// Get pointer to frame slot in shared memory
inline uint8_t* get_frame_ptr(uint8_t* base, uint32_t index, uint32_t frame_size, uint32_t frame_count) {
return base + RING_HEADER_SIZE + ((index % frame_count) * frame_size);
}
// Decoder state
struct DecoderState {
std::string video_path;
int32_t width;
int32_t height;
int32_t rotation; // 0, 90, 180, 270
bool loop;
bool running;
// Shared memory
int shmem_fd;
uint8_t* shmem_base;
size_t shmem_size;
RingBufferHeader* header;
// Decoder thread
pthread_t decoder_thread;
};
// Initialize the video decoder
// Returns 0 on success, negative on error
int init_decoder(const char* video_path, bool loop = true);
// Start the decoder thread
// Returns 0 on success
int start_decoder();
// Stop the decoder thread
void stop_decoder();
// Release decoder resources
void release_decoder();
// Get the shared memory fd (for passing to other processes)
// Returns -1 if not initialized
int get_shmem_fd();
// Get decoder state info
int get_decoder_width();
int get_decoder_height();
bool is_decoder_running();
// Write a YUV420 frame to the shared memory ring buffer
// Called internally by the decoder thread
// y_data, u_data, v_data: pointers to Y, U, V planes
// y_stride, uv_stride: stride of Y and UV planes
// width, height: frame dimensions
bool write_frame_to_ring(const uint8_t* y_data, const uint8_t* u_data, const uint8_t* v_data,
int y_stride, int uv_stride, int width, int height);
} // namespace video_decoder
#endif // VIDEO_DECODER_H
+173
View File
@@ -0,0 +1,173 @@
#include "buffer_converter.h"
#ifdef __aarch64__
#include <arm_neon.h>
#endif
#include <cstring>
#include <algorithm>
namespace buffer_converter {
void copy_plane_neon(
const uint8_t* src, int src_stride,
uint8_t* dst, int dst_stride,
int width, int height) {
#ifdef __aarch64__
for (int row = 0; row < height; row++) {
const uint8_t* src_row = src + row * src_stride;
uint8_t* dst_row = dst + row * dst_stride;
int col = 0;
for (; col + 15 < width; col += 16) {
uint8x16_t data = vld1q_u8(src_row + col);
vst1q_u8(dst_row + col, data);
}
for (; col < width; col++) {
dst_row[col] = src_row[col];
}
}
#else
for (int row = 0; row < height; row++) {
std::memcpy(dst + row * dst_stride, src + row * src_stride, width);
}
#endif
}
void interleave_uv_to_nv21_neon(
const uint8_t* src_u, int src_u_stride,
const uint8_t* src_v, int src_v_stride,
uint8_t* dst_vu, int dst_vu_stride,
int width, int height) {
int uv_width = width / 2;
int uv_height = height / 2;
#ifdef __aarch64__
for (int row = 0; row < uv_height; row++) {
const uint8_t* u_row = src_u + row * src_u_stride;
const uint8_t* v_row = src_v + row * src_v_stride;
uint8_t* dst_row = dst_vu + row * dst_vu_stride;
int col = 0;
for (; col + 15 < uv_width; col += 16) {
uint8x16_t u_data = vld1q_u8(u_row + col);
uint8x16_t v_data = vld1q_u8(v_row + col);
uint8x16x2_t vu_interleaved;
vu_interleaved.val[0] = v_data;
vu_interleaved.val[1] = u_data;
vst2q_u8(dst_row + col * 2, vu_interleaved);
}
for (; col < uv_width; col++) {
dst_row[col * 2] = v_row[col];
dst_row[col * 2 + 1] = u_row[col];
}
}
#else
for (int row = 0; row < uv_height; row++) {
const uint8_t* u_row = src_u + row * src_u_stride;
const uint8_t* v_row = src_v + row * src_v_stride;
uint8_t* dst_row = dst_vu + row * dst_vu_stride;
for (int col = 0; col < uv_width; col++) {
dst_row[col * 2] = v_row[col];
dst_row[col * 2 + 1] = u_row[col];
}
}
#endif
}
bool yuv420_planar_to_nv21(
const uint8_t* src_y, int src_y_stride,
const uint8_t* src_u, int src_u_stride,
const uint8_t* src_v, int src_v_stride,
uint8_t* dst_nv21, int dst_y_stride, int dst_uv_stride,
int width, int height) {
if (!src_y || !src_u || !src_v || !dst_nv21 || width <= 0 || height <= 0) {
return false;
}
copy_plane_neon(src_y, src_y_stride, dst_nv21, dst_y_stride, width, height);
uint8_t* dst_vu = dst_nv21 + (dst_y_stride * height);
interleave_uv_to_nv21_neon(src_u, src_u_stride, src_v, src_v_stride,
dst_vu, dst_uv_stride, width, height);
return true;
}
bool yuv420_planar_copy_with_stride(
const uint8_t* src_y, int src_y_stride,
const uint8_t* src_u, int src_u_stride,
const uint8_t* src_v, int src_v_stride,
uint8_t* dst_y, int dst_y_stride,
uint8_t* dst_u, int dst_u_stride,
uint8_t* dst_v, int dst_v_stride,
int width, int height) {
if (!src_y || !src_u || !src_v || !dst_y || !dst_u || !dst_v ||
width <= 0 || height <= 0) {
return false;
}
copy_plane_neon(src_y, src_y_stride, dst_y, dst_y_stride, width, height);
int uv_width = width / 2;
int uv_height = height / 2;
copy_plane_neon(src_u, src_u_stride, dst_u, dst_u_stride, uv_width, uv_height);
copy_plane_neon(src_v, src_v_stride, dst_v, dst_v_stride, uv_width, uv_height);
return true;
}
bool nv21_to_yuv420_planar(
const uint8_t* src_nv21, int src_y_stride, int src_uv_stride,
uint8_t* dst_y, int dst_y_stride,
uint8_t* dst_u, int dst_u_stride,
uint8_t* dst_v, int dst_v_stride,
int width, int height) {
if (!src_nv21 || !dst_y || !dst_u || !dst_v || width <= 0 || height <= 0) {
return false;
}
copy_plane_neon(src_nv21, src_y_stride, dst_y, dst_y_stride, width, height);
int uv_width = width / 2;
int uv_height = height / 2;
const uint8_t* src_vu = src_nv21 + (src_y_stride * height);
#ifdef __aarch64__
for (int row = 0; row < uv_height; row++) {
const uint8_t* vu_row = src_vu + row * src_uv_stride;
uint8_t* u_row = dst_u + row * dst_u_stride;
uint8_t* v_row = dst_v + row * dst_v_stride;
int col = 0;
for (; col + 15 < uv_width; col += 16) {
uint8x16x2_t vu = vld2q_u8(vu_row + col * 2);
vst1q_u8(v_row + col, vu.val[0]);
vst1q_u8(u_row + col, vu.val[1]);
}
for (; col < uv_width; col++) {
v_row[col] = vu_row[col * 2];
u_row[col] = vu_row[col * 2 + 1];
}
}
#else
for (int row = 0; row < uv_height; row++) {
const uint8_t* vu_row = src_vu + row * src_uv_stride;
uint8_t* u_row = dst_u + row * dst_u_stride;
uint8_t* v_row = dst_v + row * dst_v_stride;
for (int col = 0; col < uv_width; col++) {
v_row[col] = vu_row[col * 2];
u_row[col] = vu_row[col * 2 + 1];
}
}
#endif
return true;
}
} // namespace buffer_converter
+15
View File
@@ -0,0 +1,15 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define REAL_PROVIDER "/data/adb/modules/camera-hook/camera-provider-real"
#define HOOK_LIB "/data/adb/modules/camera-hook/libcamera_hook.so"
int main(int argc, char *argv[]) {
setenv("LD_PRELOAD", HOOK_LIB, 1);
execv(REAL_PROVIDER, argv);
fprintf(stderr, "CameraHook: execv(%s) failed: %s\n", REAL_PROVIDER, strerror(errno));
return 1;
}
+386
View File
@@ -0,0 +1,386 @@
#include <dlfcn.h>
#include <android/log.h>
#include <unistd.h>
#include <cstring>
#include <cstdio>
#include <map>
#include <string>
#include <vector>
#include <mutex>
#include <atomic>
#include <chrono>
#include <fstream>
#include "camera_hal/types.h"
#include "camera_hal/ICameraProvider.h"
#include "camera_hal/ICameraDevice.h"
#include "camera_hal/ICameraDeviceSession.h"
#define LOG_TAG "CameraHook"
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
static void* g_real_hal_handle = nullptr;
static bool g_hook_initialized = false;
static const char* APEX_HAL_PATH =
"/apex/com.google.pixel.camera.hal/lib64/libgooglecamerahal.so";
struct StreamInfo {
int32_t id;
int32_t width;
int32_t height;
int32_t format;
int32_t direction;
int32_t usage;
int32_t data_space;
int32_t hal_format;
int32_t stride;
};
struct HookState {
std::map<std::string, void*> open_devices;
std::map<void*, void*> device_sessions;
bool virtual_camera_enabled = false;
int32_t target_stream_format = camera_hal::FORMAT_YUV_420_888;
std::mutex stream_mutex;
std::map<int32_t, StreamInfo> stream_registry;
int32_t yuv_preview_stream_id = -1;
int32_t yuv_preview_width = 0;
int32_t yuv_preview_height = 0;
std::atomic<int64_t> frame_count{0};
std::atomic<int64_t> injected_count{0};
std::chrono::steady_clock::time_point fps_start;
std::atomic<int64_t> fps_frame_count{0};
static constexpr const char* CONFIG_PATH = "/data/local/camera_magic/config.txt";
};
static HookState g_hook_state;
static bool load_real_hal() {
if (g_real_hal_handle) {
return true;
}
g_real_hal_handle = dlopen(APEX_HAL_PATH, RTLD_NOW | RTLD_LOCAL);
if (!g_real_hal_handle) {
ALOGE("Failed to load real HAL: %s", dlerror());
return false;
}
ALOGI("Loaded real HAL from %s", APEX_HAL_PATH);
return true;
}
static void* resolve_real_symbol(const char* symbol) {
if (!g_real_hal_handle) {
return nullptr;
}
void* addr = dlsym(g_real_hal_handle, symbol);
if (!addr) {
ALOGW("Symbol not found in real HAL: %s", symbol);
}
return addr;
}
static bool check_virtual_camera_enabled() {
std::ifstream config(g_hook_state.CONFIG_PATH);
if (!config.is_open()) {
return false;
}
std::string line;
while (std::getline(config, line)) {
if (line.find("virtual_camera=1") != std::string::npos ||
line.find("enabled=true") != std::string::npos) {
return true;
}
}
return false;
}
static bool inject_video_frame(int32_t stream_id, void* buffer_handle,
int32_t width, int32_t height) {
// Task 7/9 will implement: read from shared memory ring buffer,
// lock AHardwareBuffer, copy YUV data, unlock.
// Returns true if frame was injected, false if no frame available.
(void)stream_id;
(void)buffer_handle;
(void)width;
(void)height;
return false;
}
static void update_fps_counter() {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
now - g_hook_state.fps_start).count();
if (elapsed >= 1) {
int64_t frames = g_hook_state.fps_frame_count.exchange(0);
if (frames > 0) {
ALOGI("[FPS] %.1f fps (%lld frames in %llds)",
(double)frames / elapsed, (long long)frames, (long long)elapsed);
}
g_hook_state.fps_start = now;
}
}
class CameraDeviceSessionHook {
public:
void* real_session;
explicit CameraDeviceSessionHook(void* real)
: real_session(real) {
ALOGI("[SessionHook] Created for session %p", real);
}
int32_t configureStreams(
const camera_hal::Stream* streams,
int32_t stream_count,
camera_hal::StreamConfigurationMode mode,
camera_hal::HalStreamConfiguration* out_config) {
ALOGI("[SessionHook] configureStreams: %d streams, mode=%d",
stream_count, mode);
{
std::lock_guard<std::mutex> lock(g_hook_state.stream_mutex);
g_hook_state.stream_registry.clear();
g_hook_state.yuv_preview_stream_id = -1;
for (int32_t i = 0; i < stream_count; i++) {
const auto& s = streams[i];
StreamInfo info;
info.id = s.id;
info.width = s.width;
info.height = s.height;
info.format = s.format;
info.direction = s.direction;
info.usage = s.usage;
info.data_space = s.data_space;
info.hal_format = 0;
info.stride = 0;
g_hook_state.stream_registry[s.id] = info;
ALOGI("[SessionHook] Stream[%d]: id=%d %dx%d fmt=%d dir=%d usage=0x%x",
i, s.id, s.width, s.height, s.format, s.direction, s.usage);
if (s.format == camera_hal::FORMAT_YUV_420_888 &&
s.direction == camera_hal::STREAM_OUTPUT &&
g_hook_state.yuv_preview_stream_id == -1) {
g_hook_state.yuv_preview_stream_id = s.id;
g_hook_state.yuv_preview_width = s.width;
g_hook_state.yuv_preview_height = s.height;
ALOGI("[SessionHook] -> YUV preview stream identified (id=%d)", s.id);
}
}
}
ALOGI("[SessionHook] Stream registry: %zu streams, YUV preview id=%d",
g_hook_state.stream_registry.size(),
g_hook_state.yuv_preview_stream_id);
return 0;
}
int32_t processCaptureRequest(
const camera_hal::CaptureRequest* requests,
int32_t request_count,
int32_t* out_num_request_processed) {
int64_t total = g_hook_state.frame_count.fetch_add(request_count) + request_count;
bool enabled = check_virtual_camera_enabled();
if (enabled != g_hook_state.virtual_camera_enabled) {
g_hook_state.virtual_camera_enabled = enabled;
ALOGI("[SessionHook] Virtual camera %s", enabled ? "ENABLED" : "DISABLED");
}
for (int32_t r = 0; r < request_count; r++) {
const auto& req = requests[r];
for (int32_t b = 0; b < req.output_buffer_count; b++) {
const auto& buf = req.output_buffers[b];
int32_t sid = buf.stream_id;
if (enabled && sid == g_hook_state.yuv_preview_stream_id) {
StreamInfo* info = nullptr;
{
std::lock_guard<std::mutex> lock(g_hook_state.stream_mutex);
auto it = g_hook_state.stream_registry.find(sid);
if (it != g_hook_state.stream_registry.end()) {
info = &it->second;
}
}
if (info && buf.handle) {
bool injected = inject_video_frame(
sid, buf.handle, info->width, info->height);
if (injected) {
g_hook_state.injected_count.fetch_add(1);
g_hook_state.fps_frame_count.fetch_add(1);
update_fps_counter();
}
}
}
}
if (total % 300 == 0) {
ALOGI("[SessionHook] Frames: %lld total, %lld injected",
(long long)total,
(long long)g_hook_state.injected_count.load());
}
}
if (out_num_request_processed) {
*out_num_request_processed = request_count;
}
return 0;
}
int32_t flush() {
ALOGI("[SessionHook] flush called");
return 0;
}
int32_t close() {
ALOGI("[SessionHook] close called");
return 0;
}
};
class CameraDeviceHook {
public:
void* real_device;
std::string camera_id;
CameraDeviceHook(void* real, const char* id)
: real_device(real), camera_id(id ? id : "unknown") {
ALOGI("[DeviceHook] Created for device '%s' (%p)", camera_id.c_str(), real);
}
int32_t open(void* callback) {
ALOGI("[DeviceHook] open called for '%s'", camera_id.c_str());
return 0;
}
int32_t getCameraCharacteristics(void* characteristics) {
ALOGI("[DeviceHook] getCameraCharacteristics for '%s'", camera_id.c_str());
return 0;
}
int32_t close() {
ALOGI("[DeviceHook] close called for '%s'", camera_id.c_str());
g_hook_state.open_devices.erase(camera_id);
return 0;
}
};
class CameraProviderHook {
public:
void* real_provider;
explicit CameraProviderHook(void* real)
: real_provider(real) {
ALOGI("[ProviderHook] Created for provider %p", real);
}
int32_t getCameraIdList(char*** camera_ids, int32_t* count) {
ALOGI("[ProviderHook] getCameraIdList called");
return 0;
}
int32_t getCameraDeviceInterface(
const char* camera_id,
void** out_device) {
ALOGI("[ProviderHook] getCameraDeviceInterface: '%s'", camera_id);
return 0;
}
int32_t openSession(
const char* camera_id,
void* callback,
void** out_session) {
ALOGI("[ProviderHook] openSession: '%s'", camera_id);
return 0;
}
};
__attribute__((constructor))
static void camera_hook_init() {
if (g_hook_initialized) {
return;
}
ALOGI("=== CameraHook LD_PRELOAD library loaded ===");
ALOGI("PID: %d, Process: camera provider", getpid());
if (!load_real_hal()) {
ALOGE("Cannot load real HAL, hook will not function");
return;
}
g_hook_initialized = true;
ALOGI("CameraHook initialized successfully");
}
__attribute__((destructor))
static void camera_hook_deinit() {
if (g_real_hal_handle) {
dlclose(g_real_hal_handle);
g_real_hal_handle = nullptr;
}
ALOGI("CameraHook unloaded");
}
extern "C" {
__attribute__((visibility("default")))
void* dlopen(const char* filename, int flags) {
typedef void* (*real_dlopen_t)(const char*, int);
static real_dlopen_t real_dlopen = nullptr;
if (!real_dlopen) {
real_dlopen = (real_dlopen_t)::dlsym(RTLD_NEXT, "dlopen");
}
if (filename && strstr(filename, "libgooglecamerahal.so")) {
ALOGI("[dlopen] Intercepted: %s", filename);
if (!load_real_hal()) {
return nullptr;
}
return g_real_hal_handle;
}
return real_dlopen(filename, flags);
}
__attribute__((visibility("default")))
void* dlsym(void* handle, const char* symbol) {
typedef void* (*real_dlsym_t)(void*, const char*);
static real_dlsym_t real_dlsym = nullptr;
if (!real_dlsym) {
real_dlsym = (real_dlsym_t)::dlsym(RTLD_NEXT, "dlsym");
}
void* result = real_dlsym(handle, symbol);
if (result && g_hook_initialized && symbol) {
if (strstr(symbol, "CameraProvider") ||
strstr(symbol, "CameraDevice") ||
strstr(symbol, "createProvider") ||
strstr(symbol, "getProvider")) {
ALOGI("[dlsym] HAL symbol intercepted: %s -> %p", symbol, result);
}
}
return result;
}
} // extern "C"
+15
View File
@@ -0,0 +1,15 @@
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define REAL_CAMERASERVER "/data/adb/modules/camera-hook/cameraserver-real"
#define HOOK_LIB "/data/local/camera_magic/libcamera_hook.so"
int main(int argc, char *argv[]) {
setenv("LD_PRELOAD", HOOK_LIB, 1);
execv(REAL_CAMERASERVER, argv);
fprintf(stderr, "CameraHook: execv(%s) failed: %s\n", REAL_CAMERASERVER, strerror(errno));
return 1;
}
+173
View File
@@ -0,0 +1,173 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/uio.h>
#include <elf.h>
#include <errno.h>
#define LIB_PATH "/data/local/tmp/libcamera_hook.so"
#define ARM64_BRK 0xd4200000
static int ptrace_write_data(long pid, unsigned long addr, const void *buf, size_t len) {
for (size_t i = 0; i < len; i += sizeof(long)) {
long val = 0;
size_t cpy = (len - i < sizeof(long)) ? len - i : sizeof(long);
memcpy(&val, (const char*)buf + i, cpy);
if (ptrace(PTRACE_POKETEXT, pid, addr + i, val) < 0) {
fprintf(stderr, "POKETEXT failed at 0x%lx\n", addr + i);
return -1;
}
}
return 0;
}
static unsigned long find_linker_rx_base(long pid, unsigned long *file_offset_out) {
char path[256];
snprintf(path, sizeof(path), "/proc/%ld/maps", pid);
FILE *f = fopen(path, "r");
if (!f) { perror("fopen maps"); return 0; }
char line[1024];
while (fgets(line, sizeof(line), f)) {
if (strstr(line, "/apex/com.android.runtime/bin/linker64")) {
unsigned long start, end, offset;
char perm[8];
sscanf(line, "%lx-%lx %4s %lx", &start, &end, perm, &offset);
if (strstr(perm, "r-xp")) {
fclose(f);
fprintf(stderr, "linker64 r-xp: 0x%lx (file offset 0x%lx)\n", start, offset);
if (file_offset_out) *file_offset_out = offset;
return start;
}
}
}
fclose(f);
fprintf(stderr, "linker64 r-xp not found\n");
return 0;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <pid>\n", argv[0]);
return 1;
}
long pid = atol(argv[1]);
unsigned long rx_offset = 0;
unsigned long rx_base = find_linker_rx_base(pid, &rx_offset);
if (!rx_base) {
fprintf(stderr, "Failed to find linker64 r-xp\n");
return 1;
}
unsigned long dlopen_offset = 0x86fa0;
unsigned long dlopen_addr = rx_base + (dlopen_offset - rx_offset);
fprintf(stderr, "dlopen address: 0x%lx (rx_base=0x%lx, sym_offset=0x%lx, rx_offset=0x%lx)\n",
dlopen_addr, rx_base, dlopen_offset, rx_offset);
fprintf(stderr, "Attaching to PID %ld...\n", pid);
if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) {
perror("PTRACE_ATTACH");
return 1;
}
int status;
waitpid(pid, &status, WUNTRACED);
if (!WIFSTOPPED(status)) {
fprintf(stderr, "Target did not stop after attach\n");
ptrace(PTRACE_DETACH, pid, 0, 0);
return 1;
}
fprintf(stderr, "Attached, target stopped (signal=%d)\n", WSTOPSIG(status));
struct user_pt_regs saved_regs;
struct iovec iov_save = { &saved_regs, sizeof(saved_regs) };
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov_save) < 0) {
perror("PTRACE_GETREGSET (save)");
ptrace(PTRACE_DETACH, pid, 0, 0);
return 1;
}
fprintf(stderr, "Saved: PC=0x%lx SP=0x%lx\n", saved_regs.pc, saved_regs.sp);
unsigned long data_addr = saved_regs.sp - 0x8000;
fprintf(stderr, "Data area at 0x%lx\n", data_addr);
size_t path_len = strlen(LIB_PATH) + 1;
if (ptrace_write_data(pid, data_addr, LIB_PATH, path_len) < 0) {
fprintf(stderr, "Failed to write lib path\n");
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
ptrace(PTRACE_DETACH, pid, 0, 0);
return 1;
}
fprintf(stderr, "Wrote '%s' to 0x%lx\n", LIB_PATH, data_addr);
unsigned long ret_addr = data_addr + 0x200;
errno = 0;
long orig_at_ret = ptrace(PTRACE_PEEKTEXT, pid, ret_addr, 0);
if (errno) {
perror("PEEKTEXT at return address");
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
ptrace(PTRACE_DETACH, pid, 0, 0);
return 1;
}
if (ptrace(PTRACE_POKETEXT, pid, ret_addr, (void*)(long)ARM64_BRK) < 0) {
perror("POKETEXT breakpoint");
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
ptrace(PTRACE_DETACH, pid, 0, 0);
return 1;
}
fprintf(stderr, "Breakpoint at 0x%lx (orig=0x%lx)\n", ret_addr, orig_at_ret);
struct user_pt_regs call_regs = saved_regs;
call_regs.regs[0] = data_addr;
call_regs.regs[1] = 2;
call_regs.regs[2] = saved_regs.regs[30];
call_regs.regs[3] = 0;
call_regs.regs[30] = ret_addr;
call_regs.pc = dlopen_addr;
struct iovec iov_call = { &call_regs, sizeof(call_regs) };
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_call) < 0) {
perror("PTRACE_SETREGSET (call)");
ptrace(PTRACE_POKETEXT, pid, ret_addr, (void*)(long)orig_at_ret);
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
ptrace(PTRACE_DETACH, pid, 0, 0);
return 1;
}
fprintf(stderr, "Calling dlopen(\"%s\", 2) at 0x%lx...\n", LIB_PATH, dlopen_addr);
ptrace(PTRACE_CONT, pid, 0, 0);
waitpid(pid, &status, WUNTRACED);
if (WIFSTOPPED(status)) {
fprintf(stderr, "Target stopped (signal=%d)\n", WSTOPSIG(status));
} else if (WIFEXITED(status)) {
fprintf(stderr, "Target exited! (code=%d)\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
fprintf(stderr, "Target killed! (signal=%d)\n", WTERMSIG(status));
}
ptrace(PTRACE_POKETEXT, pid, ret_addr, (void*)(long)orig_at_ret);
struct user_pt_regs result_regs;
struct iovec iov_result = { &result_regs, sizeof(result_regs) };
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov_result) < 0) {
perror("PTRACE_GETREGSET (result)");
} else {
fprintf(stderr, "dlopen returned: 0x%llx\n", (unsigned long long)result_regs.regs[0]);
if (result_regs.regs[0] != 0) {
fprintf(stderr, "SUCCESS! Library loaded.\n");
} else {
fprintf(stderr, "FAILED: dlopen returned NULL\n");
}
}
ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov_save);
ptrace(PTRACE_DETACH, pid, 0, 0);
fprintf(stderr, "Detached from PID %ld\n", pid);
return 0;
}
+653
View File
@@ -0,0 +1,653 @@
#include "rtsp_client.h"
#include "video_decoder.h"
#include <android/log.h>
#include <media/NdkMediaCodec.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <cstring>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <poll.h>
#define LOG_TAG "RtspClient"
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
static rtsp_client::RtspState g_rtsp = {};
static AMediaCodec* g_codec = nullptr;
static uint8_t g_rtp_buf[rtsp_client::RTP_BUFFER_SIZE];
static uint8_t g_nal_buf[256 * 1024];
static int g_nal_pos = 0;
static int memfd_create_compat(const char* name, unsigned int flags) {
#ifdef __NR_memfd_create
return (int)syscall(__NR_memfd_create, name, flags);
#else
(void)flags;
char path[256];
snprintf(path, sizeof(path), "/data/local/tmp/camera_magic_rtsp_%s_%d", name, getpid());
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666);
if (fd >= 0) unlink(path);
return fd;
#endif
}
static bool parse_rtsp_url(const char* url, std::string& host, int& port, std::string& path) {
const char* p = url;
if (strncmp(p, "rtsp://", 7) != 0) return false;
p += 7;
const char* colon = strchr(p, ':');
const char* slash = strchr(p, '/');
if (colon && (!slash || colon < slash)) {
host.assign(p, colon - p);
port = atoi(colon + 1);
p = colon + 1;
while (*p >= '0' && *p <= '9') p++;
} else {
if (slash) {
host.assign(p, slash - p);
} else {
host = p;
}
port = rtsp_client::RTSP_DEFAULT_PORT;
}
if (*p == '/') {
path = p;
} else {
path = "/";
}
return true;
}
static int rtsp_send_request(int fd, const char* method, const char* url,
const char* extra_headers, int* cseq) {
char req[2048];
int len = snprintf(req, sizeof(req),
"%s %s RTSP/1.0\r\n"
"CSeq: %d\r\n"
"%s"
"\r\n",
method, url, *cseq, extra_headers ? extra_headers : "");
(*cseq)++;
int sent = send(fd, req, len, MSG_NOSIGNAL);
if (sent != len) {
ALOGE("send failed: %d/%d (%s)", sent, len, strerror(errno));
return -1;
}
ALOGD("Sent: %s CSeq=%d", method, *cseq - 1);
return 0;
}
static int rtsp_read_response(int fd, char* buf, int buf_size, int* status_code,
char* session_buf, int session_buf_size) {
int total = 0;
int timeout_ms = 5000;
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
while (total < buf_size - 1) {
int ret = poll(&pfd, 1, timeout_ms);
if (ret <= 0) {
ALOGE("poll timeout/error: %d", ret);
return -1;
}
ret = recv(fd, buf + total, buf_size - 1 - total, 0);
if (ret <= 0) {
ALOGE("recv failed: %s", strerror(errno));
return -1;
}
total += ret;
buf[total] = '\0';
if (strstr(buf, "\r\n\r\n")) break;
}
if (sscanf(buf, "RTSP/1.0 %d", status_code) != 1) {
ALOGE("Cannot parse status from response");
return -1;
}
if (session_buf && session_buf_size > 0) {
const char* sess = strstr(buf, "Session: ");
if (sess) {
sess += 9;
int i = 0;
while (*sess && *sess != '\r' && *sess != ';' && i < session_buf_size - 1) {
session_buf[i++] = *sess++;
}
session_buf[i] = '\0';
}
}
ALOGD("Response: %d", *status_code);
return total;
}
static int create_udp_socket(int* port) {
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) return -1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
for (int p = 50000; p < 65000; p += 2) {
addr.sin_port = htons(p);
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
*port = p;
return fd;
}
}
close(fd);
return -1;
}
static bool init_rtsp_shmem() {
int w = g_rtsp.width > 0 ? g_rtsp.width : 1920;
int h = g_rtsp.height > 0 ? g_rtsp.height : 1080;
size_t size = video_decoder::calc_shmem_size(w, h);
int fd = memfd_create_compat("cam2magic_rtsp", 0);
if (fd < 0) {
ALOGE("memfd_create failed: %s", strerror(errno));
return false;
}
if (ftruncate(fd, size) < 0) {
ALOGE("ftruncate failed: %s", strerror(errno));
close(fd);
return false;
}
uint8_t* base = (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (base == MAP_FAILED) {
ALOGE("mmap failed: %s", strerror(errno));
close(fd);
return false;
}
memset(base, 0, size);
auto* header = reinterpret_cast<video_decoder::RingBufferHeader*>(base);
header->magic = video_decoder::RING_MAGIC;
header->version = video_decoder::RING_VERSION;
header->frame_count = video_decoder::RING_FRAME_COUNT;
header->frame_width = w;
header->frame_height = h;
header->frame_size = (uint32_t)(w * h * 3 / 2);
g_rtsp.shmem_fd = fd;
g_rtsp.shmem_base = base;
g_rtsp.shmem_size = size;
ALOGI("RTSP shared memory: fd=%d, size=%zu, %dx%d", fd, size, w, h);
return true;
}
static void release_rtsp_shmem() {
if (g_rtsp.shmem_base && g_rtsp.shmem_base != MAP_FAILED) {
munmap(g_rtsp.shmem_base, g_rtsp.shmem_size);
g_rtsp.shmem_base = nullptr;
}
if (g_rtsp.shmem_fd >= 0) {
close(g_rtsp.shmem_fd);
g_rtsp.shmem_fd = -1;
}
g_rtsp.shmem_size = 0;
}
static void feed_nal_to_decoder(const uint8_t* nal, int nal_len) {
if (!g_codec || nal_len <= 0) return;
ssize_t idx = AMediaCodec_dequeueInputBuffer(g_codec, 1000);
if (idx < 0) return;
size_t buf_size = 0;
uint8_t* buf = AMediaCodec_getInputBuffer(g_codec, idx, &buf_size);
if (!buf || (size_t)nal_len > buf_size) return;
memcpy(buf, nal, nal_len);
uint32_t flags = 0;
if (nal[0] == 0 && nal[1] == 0 && nal[2] == 0 && nal[3] == 1) {
int type = nal[4] & 0x1F;
if (type == 5 || type == 7 || type == 8) {
flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
}
}
AMediaCodec_queueInputBuffer(g_codec, idx, 0, nal_len, 0, flags);
}
static void drain_decoder_output() {
if (!g_codec) return;
AMediaCodecBufferInfo info;
ssize_t idx = AMediaCodec_dequeueOutputBuffer(g_codec, &info, 0);
if (idx >= 0) {
size_t out_size = 0;
uint8_t* out_buf = AMediaCodec_getOutputBuffer(g_codec, idx, &out_size);
if (out_buf && info.size > 0 && g_rtsp.shmem_base) {
int w = g_rtsp.width;
int h = g_rtsp.height;
if (w > 0 && h > 0 && out_size >= (size_t)(w * h * 3 / 2)) {
const uint8_t* y = out_buf;
const uint8_t* u = out_buf + w * h;
const uint8_t* v = u + (w / 2) * (h / 2);
video_decoder::write_frame_to_ring(y, u, v, w, w / 2, w, h);
g_rtsp.frame_count++;
if (g_rtsp.frame_count % 300 == 0) {
ALOGI("RTSP decoded %u frames", g_rtsp.frame_count);
}
}
}
AMediaCodec_releaseOutputBuffer(g_codec, idx, false);
} else if (idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
AMediaFormat* fmt = AMediaCodec_getOutputFormat(g_codec);
int32_t w = 0, h = 0;
AMediaFormat_getInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, &w);
AMediaFormat_getInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, &h);
ALOGI("RTSP output format changed: %dx%d", w, h);
if (w > 0 && h > 0) {
g_rtsp.width = w;
g_rtsp.height = h;
}
AMediaFormat_delete(fmt);
}
}
static void* rtsp_receiver_thread(void* arg) {
(void)arg;
ALOGI("RTSP receiver thread started");
while (g_rtsp.running) {
struct pollfd pfd;
pfd.fd = g_rtsp.rtp_fd;
pfd.events = POLLIN;
int ret = poll(&pfd, 1, 1000);
if (ret <= 0) continue;
ret = recv(g_rtsp.rtp_fd, g_rtp_buf, sizeof(g_rtp_buf), 0);
if (ret <= 0) continue;
if (ret < 12) continue;
int payload_type = g_rtp_buf[1] & 0x7F;
if (payload_type != 96 && payload_type != 97 && payload_type != 98 && payload_type != 99 &&
payload_type != 100 && payload_type != 101 && payload_type != 102 && payload_type != 103 &&
payload_type != 104 && payload_type != 105 && payload_type != 106 && payload_type != 107 &&
payload_type != 108 && payload_type != 109 && payload_type != 110 && payload_type != 111 &&
payload_type != 112 && payload_type != 113 && payload_type != 114 && payload_type != 115 &&
payload_type != 116 && payload_type != 117 && payload_type != 118 && payload_type != 119 &&
payload_type != 120 && payload_type != 121 && payload_type != 122 && payload_type != 123 &&
payload_type != 124 && payload_type != 125 && payload_type != 126 && payload_type != 127) {
continue;
}
const uint8_t* payload = g_rtp_buf + 12;
int payload_len = ret - 12;
if (payload_len < 2) continue;
int nal_type = payload[0] & 0x1F;
if (nal_type == 28 || nal_type == 29) {
int fu_header = payload[1];
int start = fu_header & 0x80;
int end = fu_header & 0x40;
int nal_hdr = (payload[0] & 0xE0) | (fu_header & 0x1F);
if (start) {
g_nal_buf[0] = 0;
g_nal_buf[1] = 0;
g_nal_buf[2] = 0;
g_nal_buf[3] = 1;
g_nal_buf[4] = nal_hdr;
g_nal_pos = 5;
int copy_len = payload_len - 2;
if (g_nal_pos + copy_len < (int)sizeof(g_nal_buf)) {
memcpy(g_nal_buf + g_nal_pos, payload + 2, copy_len);
g_nal_pos += copy_len;
}
} else {
int copy_len = payload_len - 2;
if (g_nal_pos + copy_len < (int)sizeof(g_nal_buf)) {
memcpy(g_nal_buf + g_nal_pos, payload + 2, copy_len);
g_nal_pos += copy_len;
}
}
if (end) {
feed_nal_to_decoder(g_nal_buf, g_nal_pos);
drain_decoder_output();
g_nal_pos = 0;
g_rtsp.bytes_received += ret;
}
} else if (nal_type >= 1 && nal_type <= 23) {
g_nal_buf[0] = 0;
g_nal_buf[1] = 0;
g_nal_buf[2] = 0;
g_nal_buf[3] = 1;
int copy_len = payload_len;
if (4 + copy_len < (int)sizeof(g_nal_buf)) {
memcpy(g_nal_buf + 4, payload, copy_len);
feed_nal_to_decoder(g_nal_buf, 4 + copy_len);
drain_decoder_output();
g_rtsp.bytes_received += ret;
}
}
}
ALOGI("RTSP receiver thread finished");
return nullptr;
}
int rtsp_client::init_rtsp(const char* url) {
if (!url) return -1;
memset(&g_rtsp, 0, sizeof(g_rtsp));
g_rtsp.url = url;
g_rtsp.rtsp_fd = -1;
g_rtsp.rtp_fd = -1;
g_rtsp.rtcp_fd = -1;
g_rtsp.shmem_fd = -1;
g_rtsp.width = 1920;
g_rtsp.height = 1080;
if (!parse_rtsp_url(url, g_rtsp.host, g_rtsp.port, g_rtsp.path)) {
ALOGE("Invalid RTSP URL: %s", url);
return -1;
}
ALOGI("RTSP init: %s:%d%s", g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
return 0;
}
int rtsp_client::connect_rtsp() {
int cseq = 1;
char resp_buf[4096];
int status = 0;
char session_buf[256] = {0};
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(g_rtsp.port);
struct hostent* he = gethostbyname(g_rtsp.host.c_str());
if (!he) {
ALOGE("Cannot resolve %s", g_rtsp.host.c_str());
return -1;
}
memcpy(&server.sin_addr, he->h_addr, he->h_length);
g_rtsp.rtsp_fd = socket(AF_INET, SOCK_STREAM, 0);
if (g_rtsp.rtsp_fd < 0) {
ALOGE("socket failed: %s", strerror(errno));
return -1;
}
if (connect(g_rtsp.rtsp_fd, (struct sockaddr*)&server, sizeof(server)) < 0) {
ALOGE("connect failed: %s", strerror(errno));
close(g_rtsp.rtsp_fd);
g_rtsp.rtsp_fd = -1;
return -1;
}
ALOGI("Connected to %s:%d", g_rtsp.host.c_str(), g_rtsp.port);
char desc_url[1024];
snprintf(desc_url, sizeof(desc_url), "rtsp://%s:%d%s",
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
if (rtsp_send_request(g_rtsp.rtsp_fd, "DESCRIBE", desc_url,
"Accept: application/sdp\r\n", &cseq) < 0) {
return -1;
}
if (rtsp_read_response(g_rtsp.rtsp_fd, resp_buf, sizeof(resp_buf), &status,
session_buf, sizeof(session_buf)) < 0 || status != 200) {
ALOGE("DESCRIBE failed: %d", status);
return -1;
}
const char* control = strstr(resp_buf, "a=control:");
if (control) {
control += 10;
while (*control == ' ') control++;
char* end = strchr((char*)control, '\r');
if (end) {
g_rtsp.control_url.assign(control, end - control);
ALOGI("Control URL: %s", g_rtsp.control_url.c_str());
}
}
const char* sdp_w = strstr(resp_buf, "a=framesize:");
if (sdp_w) {
int w = 0, h = 0;
if (sscanf(sdp_w, "a=framesize:%*d %d-%d", &w, &h) == 2 && w > 0 && h > 0) {
g_rtsp.width = w;
g_rtsp.height = h;
ALOGI("SDP resolution: %dx%d", w, h);
}
}
g_rtsp.rtp_fd = create_udp_socket(&g_rtsp.local_rtp_port);
if (g_rtsp.rtp_fd < 0) {
ALOGE("Cannot create RTP socket");
return -1;
}
g_rtsp.local_rtcp_port = g_rtsp.local_rtp_port + 1;
char transport[512];
snprintf(transport, sizeof(transport),
"Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
g_rtsp.local_rtp_port, g_rtsp.local_rtcp_port);
char setup_url[1024];
if (!g_rtsp.control_url.empty() && g_rtsp.control_url.find("rtsp://") == 0) {
snprintf(setup_url, sizeof(setup_url), "%s", g_rtsp.control_url.c_str());
} else if (!g_rtsp.control_url.empty()) {
snprintf(setup_url, sizeof(setup_url), "rtsp://%s:%d/%s",
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.control_url.c_str());
} else {
snprintf(setup_url, sizeof(setup_url), "%s", desc_url);
}
if (rtsp_send_request(g_rtsp.rtsp_fd, "SETUP", setup_url, transport, &cseq) < 0) {
return -1;
}
memset(session_buf, 0, sizeof(session_buf));
if (rtsp_read_response(g_rtsp.rtsp_fd, resp_buf, sizeof(resp_buf), &status,
session_buf, sizeof(session_buf)) < 0 || status != 200) {
ALOGE("SETUP failed: %d", status);
return -1;
}
if (session_buf[0]) {
g_rtsp.session_id = session_buf;
ALOGI("Session: %s", g_rtsp.session_id.c_str());
}
if (!init_rtsp_shmem()) {
return -1;
}
const char* mime = "video/avc";
g_codec = AMediaCodec_createDecoderByType(mime);
if (!g_codec) {
ALOGE("Cannot create MediaCodec decoder");
return -1;
}
AMediaFormat* fmt = AMediaFormat_new();
AMediaFormat_setString(fmt, AMEDIAFORMAT_KEY_MIME, mime);
AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_WIDTH, g_rtsp.width);
AMediaFormat_setInt32(fmt, AMEDIAFORMAT_KEY_HEIGHT, g_rtsp.height);
media_status_t st = AMediaCodec_configure(g_codec, fmt, nullptr, nullptr, 0);
AMediaFormat_delete(fmt);
if (st != AMEDIA_OK) {
ALOGE("AMediaCodec_configure failed: %d", st);
AMediaCodec_delete(g_codec);
g_codec = nullptr;
return -1;
}
st = AMediaCodec_start(g_codec);
if (st != AMEDIA_OK) {
ALOGE("AMediaCodec_start failed: %d", st);
AMediaCodec_delete(g_codec);
g_codec = nullptr;
return -1;
}
g_rtsp.connected = true;
ALOGI("RTSP connected, codec started");
return 0;
}
int rtsp_client::play_rtsp() {
if (!g_rtsp.connected) return -1;
int cseq = 4;
char resp_buf[4096];
int status = 0;
char session_buf[256] = {0};
char play_url[1024];
if (!g_rtsp.control_url.empty() && g_rtsp.control_url.find("rtsp://") == 0) {
snprintf(play_url, sizeof(play_url), "%s", g_rtsp.control_url.c_str());
} else {
snprintf(play_url, sizeof(play_url), "rtsp://%s:%d%s",
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
}
char session_hdr[512];
snprintf(session_hdr, sizeof(session_hdr), "Session: %s\r\n", g_rtsp.session_id.c_str());
if (rtsp_send_request(g_rtsp.rtsp_fd, "PLAY", play_url, session_hdr, &cseq) < 0) {
return -1;
}
if (rtsp_read_response(g_rtsp.rtsp_fd, resp_buf, sizeof(resp_buf), &status,
session_buf, sizeof(session_buf)) < 0 || status != 200) {
ALOGE("PLAY failed: %d", status);
return -1;
}
g_rtsp.running = true;
g_rtsp.playing = true;
int ret = pthread_create(&g_rtsp.receiver_thread, nullptr, rtsp_receiver_thread, nullptr);
if (ret != 0) {
ALOGE("pthread_create failed: %s", strerror(ret));
g_rtsp.running = false;
return -1;
}
ALOGI("RTSP playing, receiver thread started");
return 0;
}
void rtsp_client::stop_rtsp() {
if (!g_rtsp.running) return;
ALOGI("Stopping RTSP");
g_rtsp.running = false;
g_rtsp.playing = false;
if (g_rtsp.receiver_thread) {
pthread_join(g_rtsp.receiver_thread, nullptr);
g_rtsp.receiver_thread = 0;
}
if (g_codec) {
AMediaCodec_stop(g_codec);
AMediaCodec_delete(g_codec);
g_codec = nullptr;
}
if (g_rtsp.rtsp_fd >= 0) {
int cseq = 10;
char teardown_url[1024];
snprintf(teardown_url, sizeof(teardown_url), "rtsp://%s:%d%s",
g_rtsp.host.c_str(), g_rtsp.port, g_rtsp.path.c_str());
char session_hdr[512];
snprintf(session_hdr, sizeof(session_hdr), "Session: %s\r\n", g_rtsp.session_id.c_str());
rtsp_send_request(g_rtsp.rtsp_fd, "TEARDOWN", teardown_url, session_hdr, &cseq);
close(g_rtsp.rtsp_fd);
g_rtsp.rtsp_fd = -1;
}
if (g_rtsp.rtp_fd >= 0) {
close(g_rtsp.rtp_fd);
g_rtsp.rtp_fd = -1;
}
release_rtsp_shmem();
g_rtsp.connected = false;
ALOGI("RTSP stopped");
}
void rtsp_client::release_rtsp() {
stop_rtsp();
g_rtsp.url.clear();
g_rtsp.host.clear();
g_rtsp.path.clear();
g_rtsp.session_id.clear();
g_rtsp.control_url.clear();
ALOGI("RTSP released");
}
bool rtsp_client::is_rtsp_connected() {
return g_rtsp.connected && g_rtsp.playing;
}
int rtsp_client::get_rtsp_shmem_fd() {
return g_rtsp.shmem_fd;
}
int rtsp_client::get_rtsp_width() {
return g_rtsp.width;
}
int rtsp_client::get_rtsp_height() {
return g_rtsp.height;
}
+401
View File
@@ -0,0 +1,401 @@
#include "video_decoder.h"
#include <android/log.h>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaExtractor.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <cstring>
#include <cerrno>
#define LOG_TAG "VideoDecoder"
#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
static video_decoder::DecoderState g_decoder = {};
static int memfd_create_compat(const char* name, unsigned int flags) {
#ifdef __NR_memfd_create
return (int)syscall(__NR_memfd_create, name, flags);
#else
(void)flags;
char path[256];
snprintf(path, sizeof(path), "/data/local/tmp/camera_magic_shmem_%s_%d", name, getpid());
int fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0666);
if (fd >= 0) unlink(path);
return fd;
#endif
}
static bool init_shmem(uint32_t width, uint32_t height) {
size_t size = video_decoder::calc_shmem_size(width, height);
int fd = memfd_create_compat("cam2magic", 0);
if (fd < 0) {
ALOGE("memfd_create failed: %s", strerror(errno));
return false;
}
if (ftruncate(fd, size) < 0) {
ALOGE("ftruncate failed: %s", strerror(errno));
close(fd);
return false;
}
uint8_t* base = (uint8_t*)mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (base == MAP_FAILED) {
ALOGE("mmap failed: %s", strerror(errno));
close(fd);
return false;
}
memset(base, 0, size);
auto* header = reinterpret_cast<video_decoder::RingBufferHeader*>(base);
header->magic = video_decoder::RING_MAGIC;
header->version = video_decoder::RING_VERSION;
header->frame_count = video_decoder::RING_FRAME_COUNT;
header->frame_width = width;
header->frame_height = height;
header->frame_size = (uint32_t)(width * height * 3 / 2);
header->write_index = 0;
header->read_index = 0;
header->sequence = 0;
header->flags = 0;
header->last_timestamp = 0;
g_decoder.shmem_fd = fd;
g_decoder.shmem_base = base;
g_decoder.shmem_size = size;
g_decoder.header = header;
ALOGI("Shared memory initialized: fd=%d, size=%zu, %ux%u", fd, size, width, height);
return true;
}
static void release_shmem() {
if (g_decoder.shmem_base && g_decoder.shmem_base != MAP_FAILED) {
munmap(g_decoder.shmem_base, g_decoder.shmem_size);
g_decoder.shmem_base = nullptr;
}
if (g_decoder.shmem_fd >= 0) {
close(g_decoder.shmem_fd);
g_decoder.shmem_fd = -1;
}
g_decoder.header = nullptr;
g_decoder.shmem_size = 0;
}
bool video_decoder::write_frame_to_ring(const uint8_t* y_data, const uint8_t* u_data,
const uint8_t* v_data, int y_stride, int uv_stride,
int width, int height) {
if (!g_decoder.header || !g_decoder.shmem_base) return false;
uint32_t frame_size = g_decoder.header->frame_size;
uint32_t frame_count = g_decoder.header->frame_count;
uint32_t idx = g_decoder.header->write_index % frame_count;
uint8_t* dst = get_frame_ptr(g_decoder.shmem_base, idx, frame_size, frame_count);
int y_plane_size = width * height;
int uv_plane_size = (width / 2) * (height / 2);
uint8_t* dst_y = dst;
uint8_t* dst_u = dst + y_plane_size;
uint8_t* dst_v = dst + y_plane_size + uv_plane_size;
for (int row = 0; row < height; row++) {
memcpy(dst_y + row * width, y_data + row * y_stride, width);
}
for (int row = 0; row < height / 2; row++) {
memcpy(dst_u + row * (width / 2), u_data + row * uv_stride, width / 2);
memcpy(dst_v + row * (width / 2), v_data + row * uv_stride, width / 2);
}
g_decoder.header->write_index = (g_decoder.header->write_index + 1) % (frame_count * 2);
g_decoder.header->sequence++;
return true;
}
static void* decoder_thread_func(void* arg) {
(void)arg;
ALOGI("Decoder thread started");
g_decoder.header->flags |= video_decoder::FLAG_DECODER_RUNNING;
AMediaExtractor* extractor = AMediaExtractor_new();
if (!extractor) {
ALOGE("Failed to create MediaExtractor");
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
int fd = open(g_decoder.video_path.c_str(), O_RDONLY);
if (fd < 0) {
ALOGE("Failed to open video file: %s (%s)", g_decoder.video_path.c_str(), strerror(errno));
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, 0, 0);
if (status != AMEDIA_OK) {
ALOGE("AMediaExtractor_setDataSourceFd failed: %d", status);
close(fd);
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
close(fd);
int video_track = -1;
int num_tracks = AMediaExtractor_getTrackCount(extractor);
for (int i = 0; i < num_tracks; i++) {
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, i);
if (!format) continue;
const char* mime = nullptr;
if (AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime) && mime) {
if (strncmp(mime, "video/", 6) == 0) {
video_track = i;
int32_t w = 0, h = 0;
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &w);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &h);
ALOGI("Video track %d: %s, %dx%d", i, mime, w, h);
if (w > 0 && h > 0) {
g_decoder.width = w;
g_decoder.height = h;
}
AMediaFormat_delete(format);
break;
}
}
AMediaFormat_delete(format);
}
if (video_track < 0) {
ALOGE("No video track found");
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
AMediaExtractor_selectTrack(extractor, video_track);
AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, video_track);
const char* mime = nullptr;
AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
if (!mime) {
ALOGE("Cannot get MIME type from format");
AMediaFormat_delete(format);
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
AMediaCodec* codec = AMediaCodec_createDecoderByType(mime);
if (!codec) {
ALOGE("Failed to create MediaCodec decoder");
AMediaFormat_delete(format);
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
status = AMediaCodec_configure(codec, format, nullptr, nullptr, 0);
if (status != AMEDIA_OK) {
ALOGE("AMediaCodec_configure failed: %d", status);
AMediaCodec_delete(codec);
AMediaFormat_delete(format);
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
AMediaFormat_delete(format);
status = AMediaCodec_start(codec);
if (status != AMEDIA_OK) {
ALOGE("AMediaCodec_start failed: %d", status);
AMediaCodec_delete(codec);
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
return nullptr;
}
ALOGI("Decoder started, feeding frames");
bool input_done = false;
bool output_done = false;
int frame_count = 0;
const int MAX_FRAMES_PER_LOOP = 100000;
while (g_decoder.running && !output_done && frame_count < MAX_FRAMES_PER_LOOP) {
if (!input_done) {
ssize_t buf_idx = AMediaCodec_dequeueInputBuffer(codec, 2000);
if (buf_idx >= 0) {
size_t buf_size = 0;
uint8_t* buf = AMediaCodec_getInputBuffer(codec, buf_idx, &buf_size);
if (buf) {
ssize_t sample_size = AMediaExtractor_readSampleData(extractor, buf, buf_size);
if (sample_size < 0) {
ALOGI("Input EOS reached");
AMediaCodec_queueInputBuffer(codec, buf_idx, 0, 0, 0,
AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
input_done = true;
} else {
int64_t pts = AMediaExtractor_getSampleTime(extractor);
AMediaCodec_queueInputBuffer(codec, buf_idx, 0, sample_size, pts, 0);
AMediaExtractor_advance(extractor);
}
}
}
}
AMediaCodecBufferInfo info;
ssize_t out_idx = AMediaCodec_dequeueOutputBuffer(codec, &info, 2000);
if (out_idx >= 0) {
if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
ALOGI("Output EOS reached, frame_count=%d", frame_count);
output_done = true;
}
size_t out_size = 0;
uint8_t* out_buf = AMediaCodec_getOutputBuffer(codec, out_idx, &out_size);
if (out_buf && info.size > 0) {
int w = g_decoder.width;
int h = g_decoder.height;
if (w > 0 && h > 0 && out_size >= (size_t)(w * h * 3 / 2)) {
const uint8_t* y = out_buf;
const uint8_t* u = out_buf + w * h;
const uint8_t* v = u + (w / 2) * (h / 2);
video_decoder::write_frame_to_ring(y, u, v, w, w / 2, w, h);
frame_count++;
if (frame_count % 300 == 0) {
ALOGI("Decoded %d frames", frame_count);
}
}
}
AMediaCodec_releaseOutputBuffer(codec, out_idx, false);
} else if (out_idx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
AMediaFormat* new_fmt = AMediaCodec_getOutputFormat(codec);
int32_t w = 0, h = 0;
AMediaFormat_getInt32(new_fmt, AMEDIAFORMAT_KEY_WIDTH, &w);
AMediaFormat_getInt32(new_fmt, AMEDIAFORMAT_KEY_HEIGHT, &h);
ALOGI("Output format changed: %dx%d", w, h);
if (w > 0 && h > 0) {
g_decoder.width = w;
g_decoder.height = h;
}
AMediaFormat_delete(new_fmt);
}
}
if (g_decoder.loop && !output_done) {
ALOGI("Looping video (frame_count=%d)", frame_count);
}
AMediaCodec_stop(codec);
AMediaCodec_delete(codec);
AMediaExtractor_delete(extractor);
g_decoder.header->flags &= ~video_decoder::FLAG_DECODER_RUNNING;
ALOGI("Decoder thread finished, total frames: %d", frame_count);
return nullptr;
}
int video_decoder::init_decoder(const char* video_path, bool loop) {
if (!video_path) return -1;
g_decoder.video_path = video_path;
g_decoder.loop = loop;
g_decoder.running = false;
g_decoder.width = 0;
g_decoder.height = 0;
g_decoder.rotation = 0;
g_decoder.shmem_fd = -1;
g_decoder.shmem_base = nullptr;
g_decoder.shmem_size = 0;
g_decoder.header = nullptr;
ALOGI("Decoder initialized for: %s (loop=%d)", video_path, loop);
return 0;
}
int video_decoder::start_decoder() {
if (g_decoder.running) {
ALOGW("Decoder already running");
return 0;
}
if (g_decoder.width <= 0 || g_decoder.height <= 0) {
g_decoder.width = 1920;
g_decoder.height = 1080;
ALOGW("No video dimensions, defaulting to 1920x1080");
}
if (!init_shmem((uint32_t)g_decoder.width, (uint32_t)g_decoder.height)) {
return -1;
}
g_decoder.running = true;
int ret = pthread_create(&g_decoder.decoder_thread, nullptr, decoder_thread_func, nullptr);
if (ret != 0) {
ALOGE("pthread_create failed: %s", strerror(ret));
g_decoder.running = false;
release_shmem();
return -1;
}
ALOGI("Decoder thread started");
return 0;
}
void video_decoder::stop_decoder() {
if (!g_decoder.running) return;
ALOGI("Stopping decoder");
g_decoder.running = false;
if (g_decoder.decoder_thread) {
pthread_join(g_decoder.decoder_thread, nullptr);
g_decoder.decoder_thread = 0;
}
release_shmem();
ALOGI("Decoder stopped");
}
void video_decoder::release_decoder() {
stop_decoder();
g_decoder.video_path.clear();
ALOGI("Decoder released");
}
int video_decoder::get_shmem_fd() {
return g_decoder.shmem_fd;
}
int video_decoder::get_decoder_width() {
return g_decoder.width;
}
int video_decoder::get_decoder_height() {
return g_decoder.height;
}
bool video_decoder::is_decoder_running() {
return g_decoder.running && (g_decoder.header &&
(g_decoder.header->flags & FLAG_DECODER_RUNNING));
}