#include "video_decoder.h" #include #include #include #include #include #include #include #include #include #include #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(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; } uint8_t* video_decoder::get_shmem_base() { return g_decoder.shmem_base; } 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)); }