ওয়েবে USB অ্যাপ্লিকেশন পোর্টিং। পার্ট 2: gPhoto2

একটি ওয়েব অ্যাপ থেকে USB-এর মাধ্যমে বাহ্যিক ক্যামেরা নিয়ন্ত্রণ করতে কীভাবে gPhoto2 WebAssembly-এ পোর্ট করা হয়েছিল তা জানুন।

আগের পোস্টে আমি দেখিয়েছিলাম কিভাবে libusb লাইব্রেরি WebAssembly/ Emscripten , Asyncify এবং WebUSB দিয়ে ওয়েবে চালানোর জন্য পোর্ট করা হয়েছিল।

আমি gPhoto2 দিয়ে তৈরি একটি ডেমোও দেখিয়েছি যা একটি ওয়েব অ্যাপ্লিকেশন থেকে USB এর মাধ্যমে DSLR এবং মিররলেস ক্যামেরা নিয়ন্ত্রণ করতে পারে। এই পোস্টে আমি gPhoto2 পোর্টের পিছনে প্রযুক্তিগত বিশদে আরও গভীরে যাব।

কাস্টম কাঁটাচামচ বিল্ড সিস্টেম নির্দেশ করে

যেহেতু আমি WebAssembly টার্গেট করছিলাম, তাই আমি সিস্টেম ডিস্ট্রিবিউশন দ্বারা প্রদত্ত libusb এবং libgphoto2 ব্যবহার করতে পারিনি। পরিবর্তে, আমার libgphoto2 এর কাস্টম ফর্ক ব্যবহার করার জন্য আমার অ্যাপ্লিকেশনের প্রয়োজন ছিল, যখন libgphoto2-এর সেই ফর্কটি libusb-এর আমার কাস্টম ফর্ক ব্যবহার করতে হয়েছিল।

অতিরিক্তভাবে, libgphoto2 ডাইনামিক প্লাগইনগুলি লোড করার জন্য libtool ব্যবহার করে, এবং যদিও আমাকে অন্য দুটি লাইব্রেরির মতো libtool-কে ফর্ক করতে হয়নি, তবুও আমাকে এটিকে WebAssembly-এ তৈরি করতে হয়েছিল এবং libgphoto2 কে সিস্টেম প্যাকেজের পরিবর্তে সেই কাস্টম বিল্ডে নির্দেশ করতে হয়েছিল।

এখানে একটি আনুমানিক নির্ভরতা ডায়াগ্রাম (ড্যাশ করা লাইনগুলি গতিশীল লিঙ্কিং বোঝায়):

একটি ডায়াগ্রাম 'libgphoto2 ফর্ক'-এর উপর নির্ভর করে 'অ্যাপ' দেখায়, যা 'libtool'-এর উপর নির্ভর করে। 'libtool' ব্লক গতিশীলভাবে 'libgphoto2 পোর্ট' এবং 'libgphoto2 camlibs'-এর উপর নির্ভর করে। অবশেষে, 'libgphoto2 পোর্ট' স্থিরভাবে 'libusb ফর্ক'-এর উপর নির্ভর করে।

এই লাইব্রেরিতে ব্যবহৃত বেশিরভাগ কনফিগার-ভিত্তিক বিল্ড সিস্টেমগুলি বিভিন্ন পতাকার মাধ্যমে নির্ভরতার জন্য ওভাররাইডিং পাথগুলিকে অনুমতি দেয়, তাই আমি প্রথমে এটি করার চেষ্টা করেছি। যাইহোক, যখন নির্ভরতা গ্রাফ জটিল হয়ে যায়, তখন প্রতিটি লাইব্রেরির নির্ভরতার জন্য পাথ ওভাররাইডের তালিকা ভার্বস এবং ত্রুটি-প্রবণ হয়ে ওঠে। আমি এমন কিছু বাগও পেয়েছি যেখানে বিল্ড সিস্টেমগুলি অ-মানক পাথে বসবাস করার জন্য তাদের নির্ভরতার জন্য প্রস্তুত ছিল না।

পরিবর্তে, একটি সহজ পদ্ধতি হল একটি কাস্টম সিস্টেম রুট হিসাবে একটি পৃথক ফোল্ডার তৈরি করা (প্রায়শই "sysroot" এ সংক্ষিপ্ত করা হয়) এবং এতে সমস্ত জড়িত বিল্ড সিস্টেম নির্দেশ করা। এইভাবে, প্রতিটি লাইব্রেরি নির্মাণের সময় নির্দিষ্ট sysroot-এ তার নির্ভরতা উভয়ই অনুসন্ধান করবে এবং এটি একই sysroot-এ নিজেই ইনস্টল করবে যাতে অন্যরা এটি আরও সহজে খুঁজে পেতে পারে।

Emscripten-এর ইতিমধ্যেই নিজস্ব sysroot-এর অধীনে (path to emscripten cache)/sysroot রয়েছে, যা এটি তার সিস্টেম লাইব্রেরি , Emscripten পোর্ট এবং CMake এবং pkg-config-এর মতো সরঞ্জামগুলির জন্য ব্যবহার করে। আমি আমার নির্ভরতাগুলির জন্য একই sysroot পুনরায় ব্যবহার করতে বেছে নিয়েছি।

# This is the default path, but you can override it
# to store the cache elsewhere if you want.
#
# For example, it might be useful for Docker builds
# if you want to preserve the deps between reruns.
EM_CACHE = $(EMSCRIPTEN)/cache

# Sysroot is always under the `sysroot` subfolder.
SYSROOT = $(EM_CACHE)/sysroot

# …

# For all dependencies I've used the same ./configure command with the
# earlier defined SYSROOT path as the --prefix.
deps/%/Makefile: deps/%/configure
        cd $(@D) && ./configure --prefix=$(SYSROOT) # …

এই ধরনের কনফিগারেশনের সাথে, আমাকে প্রতিটি নির্ভরতাতে make install চালানোর প্রয়োজন ছিল, যা এটি sysroot এর অধীনে ইনস্টল করেছে এবং তারপরে লাইব্রেরিগুলি একে অপরকে স্বয়ংক্রিয়ভাবে খুঁজে পেয়েছে।

ডায়নামিক লোডিং নিয়ে কাজ করা

উপরে উল্লিখিত হিসাবে, libgphoto2 I/O পোর্ট অ্যাডাপ্টার এবং ক্যামেরা লাইব্রেরি গণনা এবং গতিশীলভাবে লোড করতে libtool ব্যবহার করে। উদাহরণস্বরূপ, I/O লাইব্রেরি লোড করার জন্য কোডটি এইরকম দেখাচ্ছে:

lt_dlinit ();
lt_dladdsearchdir (iolibs);
result = lt_dlforeachfile (iolibs, foreach_func, list);
lt_dlexit ();

ওয়েবে এই পদ্ধতির সাথে কয়েকটি সমস্যা রয়েছে:

  • WebAssembly মডিউলগুলির গতিশীল লিঙ্কিংয়ের জন্য কোনও মানক সমর্থন নেই। Emscripten এর কাস্টম বাস্তবায়ন রয়েছে যা libtool দ্বারা ব্যবহৃত dlopen() API-কে অনুকরণ করতে পারে, কিন্তু এর জন্য আপনাকে বিভিন্ন পতাকা সহ "প্রধান" এবং "সাইড" মডিউল তৈরি করতে হবে, এবং বিশেষ করে dlopen() এর জন্য, সাইড মডিউলগুলিও প্রিলোড করতে হবে। অ্যাপ্লিকেশান স্টার্ট-আপের সময় অনুকরণ করা ফাইল সিস্টেমে অনেকগুলি গতিশীল লাইব্রেরি সহ বিদ্যমান অটোকনফ বিল্ড সিস্টেমে এই ফ্ল্যাগগুলি এবং টুইকগুলিকে একীভূত করা কঠিন হতে পারে।
  • এমনকি dlopen() নিজেই প্রয়োগ করা হলেও, ওয়েবে একটি নির্দিষ্ট ফোল্ডারে সমস্ত গতিশীল লাইব্রেরি গণনা করার কোন উপায় নেই, কারণ বেশিরভাগ HTTP সার্ভার নিরাপত্তার কারণে ডিরেক্টরি তালিকা প্রকাশ করে না।
  • রানটাইমে গণনা করার পরিবর্তে কমান্ড লাইনে ডাইনামিক লাইব্রেরিগুলিকে লিঙ্ক করাও সমস্যার কারণ হতে পারে, যেমন ডুপ্লিকেট চিহ্ন সমস্যা , যা Emscripten এবং অন্যান্য প্ল্যাটফর্মে ভাগ করা লাইব্রেরির প্রতিনিধিত্বের মধ্যে পার্থক্যের কারণে ঘটে।

বিল্ড সিস্টেমটিকে সেই পার্থক্যগুলির সাথে খাপ খাইয়ে নেওয়া এবং বিল্ড চলাকালীন কোথাও ডায়নামিক প্লাগইনগুলির তালিকা হার্ডকোড করা সম্ভব, তবে এই সমস্ত সমস্যাগুলি সমাধান করার আরও সহজ উপায় হ'ল ডায়নামিক লিঙ্কিং এড়ানো শুরু করা।

দেখা যাচ্ছে, libtool বিভিন্ন প্ল্যাটফর্মে বিভিন্ন গতিশীল লিঙ্কিং পদ্ধতিকে বিমূর্ত করে, এমনকি অন্যদের জন্য কাস্টম লোডার লেখার সমর্থন করে। এটি সমর্থন করে এমন একটি অন্তর্নির্মিত লোডারকে বলা হয় "Dlpreopening" :

"Libtool libtool অবজেক্ট এবং libtool লাইব্রেরি ফাইলগুলিকে dlopening করার জন্য বিশেষ সহায়তা প্রদান করে, যাতে তাদের প্রতীকগুলি এমনকি প্ল্যাটফর্মে কোনো dlopen এবং dlsym ফাংশন ছাড়াই সমাধান করা যায়৷

Libtool অনুকরণ করে স্ট্যাটিক প্ল্যাটফর্মে -dlopen কম্পাইলের সময়ে প্রোগ্রামের সাথে অবজেক্ট লিঙ্ক করে এবং ডেটা স্ট্রাকচার তৈরি করে যা প্রোগ্রামের প্রতীক টেবিলকে উপস্থাপন করে। এই বৈশিষ্ট্যটি ব্যবহার করার জন্য, আপনি যখন আপনার প্রোগ্রামটি লিঙ্ক করবেন তখন -dlopen বা -dlpreopen ফ্ল্যাগগুলি ব্যবহার করে আপনি যে অবজেক্টগুলিকে আপনার অ্যাপ্লিকেশন dlopen করতে চান তা ঘোষণা করতে হবে ( লিঙ্ক মোড দেখুন)।"

এই প্রক্রিয়াটি Emscripten এর পরিবর্তে libtool স্তরে গতিশীল লোডিং অনুকরণ করার অনুমতি দেয়, যখন একটি একক লাইব্রেরিতে স্ট্যাটিকভাবে সবকিছু লিঙ্ক করে।

একমাত্র সমস্যা যা এটি সমাধান করে না তা হল গতিশীল লাইব্রেরির গণনা। সেগুলির তালিকা এখনও কোথাও হার্ডকোড করা দরকার। ভাগ্যক্রমে, অ্যাপটির জন্য আমার প্রয়োজনীয় প্লাগইনগুলির সেটটি ন্যূনতম:

  • পোর্টের দিকে, আমি শুধুমাত্র libusb-ভিত্তিক ক্যামেরা সংযোগের বিষয়ে চিন্তা করি এবং PTP/IP, সিরিয়াল অ্যাক্সেস, বা USB ড্রাইভ মোডগুলি সম্পর্কে নয়।
  • ক্যামলিব-এর দিকে, বিভিন্ন বিক্রেতা-নির্দিষ্ট প্লাগইন রয়েছে যা কিছু বিশেষ ফাংশন প্রদান করতে পারে, তবে সাধারণ সেটিংস নিয়ন্ত্রণ এবং ক্যাপচারের জন্য এটি পিকচার ট্রান্সফার প্রোটোকল ব্যবহার করা যথেষ্ট, যা ptp2 ক্যামলিব দ্বারা প্রতিনিধিত্ব করা হয় এবং কার্যত প্রতিটি ক্যামেরা দ্বারা সমর্থিত। বাজার

এখানে আপডেট হওয়া নির্ভরতা ডায়াগ্রামটি স্থিরভাবে একসাথে সংযুক্ত সবকিছুর সাথে কেমন দেখাচ্ছে:

একটি ডায়াগ্রাম 'libgphoto2 ফর্ক'-এর উপর নির্ভর করে 'অ্যাপ' দেখায়, যা 'libtool'-এর উপর নির্ভর করে। 'libtool' নির্ভর করে 'পোর্ট: libusb1' এবং 'camlibs: libptp2'-এর উপর। 'পোর্টস: libusb1' 'libusb ফর্ক'-এর উপর নির্ভর করে।

তাই আমি Emscripten বিল্ডগুলির জন্য হার্ডকোড করেছি:

LTDL_SET_PRELOADED_SYMBOLS();
lt_dlinit ();
#ifdef __EMSCRIPTEN__
  result = foreach_func("libusb1", list);
#else
  lt_dladdsearchdir (iolibs);
  result = lt_dlforeachfile (iolibs, foreach_func, list);
#endif
lt_dlexit ();

এবং

LTDL_SET_PRELOADED_SYMBOLS();
lt_dlinit ();
#ifdef __EMSCRIPTEN__
  ret = foreach_func("libptp2", &foreach_data);
#else
  lt_dladdsearchdir (dir);
  ret = lt_dlforeachfile (dir, foreach_func, &foreach_data);
#endif
lt_dlexit ();

অটোকনফ বিল্ড সিস্টেমে, আমাকে এখন সমস্ত এক্সিকিউটেবল (উদাহরণ, পরীক্ষা এবং আমার নিজস্ব ডেমো অ্যাপ) এর জন্য লিঙ্ক ফ্ল্যাগ হিসাবে এই ফাইলগুলির সাথে -dlpreopen যোগ করতে হয়েছিল:

if HAVE_EMSCRIPTEN
LDADD += -dlpreopen $(top_builddir)/libgphoto2_port/usb1.la \
         -dlpreopen $(top_builddir)/camlibs/ptp2.la
endif

অবশেষে, এখন যে সমস্ত চিহ্নগুলি একটি একক লাইব্রেরিতে স্থিরভাবে লিঙ্ক করা হয়েছে, libtool-এর একটি উপায় প্রয়োজন যে কোন প্রতীকটি কোন লাইব্রেরির অন্তর্গত তা নির্ধারণ করার জন্য। এটি অর্জন করতে, ডেভেলপারদের প্রয়োজন {function name} এর মতো সব প্রকাশ্য চিহ্নের নাম পরিবর্তন করে {library name}_LTX_{function name} । এটি করার সবচেয়ে সহজ উপায় হল বাস্তবায়ন ফাইলের শীর্ষে প্রতীকের নাম পুনরায় সংজ্ঞায়িত করতে #define ব্যবহার করে:

// …
#include "config.h"

/* Define _LTX_ names - required to prevent clashes when using libtool preloading. */
#define gp_port_library_type libusb1_LTX_gp_port_library_type
#define gp_port_library_list libusb1_LTX_gp_port_library_list
#define gp_port_library_operations libusb1_LTX_gp_port_library_operations

#include <gphoto2/gphoto2-port-library.h>
// …

আমি ভবিষ্যতে একই অ্যাপে ক্যামেরা-নির্দিষ্ট প্লাগইন লিঙ্ক করার সিদ্ধান্ত নিলে এই নামকরণ স্কিম নামের সংঘর্ষকেও বাধা দেয়।

এই সমস্ত পরিবর্তনগুলি বাস্তবায়িত হওয়ার পরে, আমি পরীক্ষা অ্যাপ্লিকেশন তৈরি করতে এবং প্লাগইনগুলি সফলভাবে লোড করতে পারি।

সেটিংস UI তৈরি করা হচ্ছে

gPhoto2 ক্যামেরা লাইব্রেরিগুলিকে উইজেট ট্রি আকারে তাদের নিজস্ব সেটিংস সংজ্ঞায়িত করতে দেয়। উইজেট প্রকারের অনুক্রমের মধ্যে রয়েছে:

  • উইন্ডো - শীর্ষ-স্তরের কনফিগারেশন ধারক
    • বিভাগগুলি - অন্যান্য উইজেটের নামযুক্ত গ্রুপ
    • বোতাম ক্ষেত্র
    • পাঠ্য ক্ষেত্র
    • সংখ্যাসূচক ক্ষেত্র
    • তারিখ ক্ষেত্র
    • টগল করে
    • রেডিও বোতাম

প্রতিটি উইজেটের নাম, প্রকার, শিশু এবং অন্যান্য সমস্ত প্রাসঙ্গিক বৈশিষ্ট্য প্রকাশ করা C API-এর মাধ্যমে জিজ্ঞাসা করা যেতে পারে (এবং, মানের ক্ষেত্রেও পরিবর্তিত)। একসাথে, তারা C এর সাথে ইন্টারঅ্যাক্ট করতে পারে এমন যেকোনো ভাষায় স্বয়ংক্রিয়ভাবে সেটিংস UI তৈরি করার জন্য একটি ভিত্তি প্রদান করে।

সেটিংস হয় gPhoto2 এর মাধ্যমে বা ক্যামেরাতে যেকোনো সময়ে পরিবর্তন করা যেতে পারে। অতিরিক্তভাবে, কিছু উইজেট শুধুমাত্র পঠনযোগ্য হতে পারে, এবং এমনকি পঠনযোগ্য অবস্থা নিজেই ক্যামেরা মোড এবং অন্যান্য সেটিংসের উপর নির্ভর করে। উদাহরণস্বরূপ, শাটার স্পিড হল M (ম্যানুয়াল মোড) তে একটি লেখার সাংখ্যিক ক্ষেত্র, কিন্তু P (প্রোগ্রাম মোড) তে একটি তথ্যমূলক পঠনযোগ্য ক্ষেত্র হয়ে ওঠে। P মোডে, ক্যামেরা যে দৃশ্য দেখছে তার উজ্জ্বলতার উপর নির্ভর করে শাটারের গতির মানও গতিশীল এবং ক্রমাগত পরিবর্তিত হবে।

সব মিলিয়ে, UI-তে সংযুক্ত ক্যামেরা থেকে সর্বদা আপ-টু-ডেট তথ্য দেখানো গুরুত্বপূর্ণ, একই সময়ে ব্যবহারকারীকে একই UI থেকে সেই সেটিংস সম্পাদনা করার অনুমতি দেয়। এই ধরনের দ্বিমুখী ডেটা প্রবাহ পরিচালনা করা আরও জটিল।

শুধুমাত্র পরিবর্তিত সেটিংস পুনরুদ্ধার করার জন্য gPhoto2 এর কোনো ব্যবস্থা নেই, শুধুমাত্র সম্পূর্ণ ট্রি বা পৃথক উইজেট। ইনপুট ফোকাস বা স্ক্রোল পজিশন না হারিয়ে UI আপ-টু-ডেট রাখার জন্য, অনুরোধগুলির মধ্যে উইজেট ট্রিগুলিকে আলাদা করার এবং শুধুমাত্র পরিবর্তিত UI বৈশিষ্ট্যগুলি আপডেট করার জন্য আমার একটি উপায় প্রয়োজন। সৌভাগ্যবশত, এটি ওয়েবে একটি সমাধান করা সমস্যা, এবং এটি হল React বা Preact এর মত ফ্রেমওয়ার্কের মূল কার্যকারিতা। আমি এই প্রজেক্টের জন্য Preact এর সাথে গিয়েছিলাম, কারণ এটি অনেক বেশি হালকা এবং আমার প্রয়োজনীয় সবকিছু করে।

C++ দিকে আমার এখন আগের লিঙ্ক করা C API-এর মাধ্যমে সেটিংস ট্রি পুনরুদ্ধার এবং পুনরাবৃত্তিমূলকভাবে হাঁটতে হবে এবং প্রতিটি উইজেটকে জাভাস্ক্রিপ্ট অবজেক্টে রূপান্তর করতে হবে:

static std::pair<val, val> walk_config(CameraWidget *widget) {
  val result = val::object();

  val name(GPP_CALL(const char *, gp_widget_get_name(widget, _)));
  result.set("name", name);
  result.set("info", /* … */);
  result.set("label", /* … */);
  result.set("readonly", /* … */);

  auto type = GPP_CALL(CameraWidgetType, gp_widget_get_type(widget, _));

  switch (type) {
    case GP_WIDGET_RANGE: {
      result.set("type", "range");
      result.set("value", GPP_CALL(float, gp_widget_get_value(widget, _)));

      float min, max, step;
      gpp_try(gp_widget_get_range(widget, &min, &max, &step));
      result.set("min", min);
      result.set("max", max);
      result.set("step", step);

      break;
    }
    case GP_WIDGET_TEXT: {
      result.set("type", "text");
      result.set("value",
                  GPP_CALL(const char *, gp_widget_get_value(widget, _)));

      break;
    }
    // …

জাভাস্ক্রিপ্টের দিকে, আমি এখন configToJS কল করতে পারি, সেটিংস ট্রির রিটার্ন করা জাভাস্ক্রিপ্ট প্রতিনিধিত্বের উপর দিয়ে যেতে পারি এবং Preact ফাংশনের মাধ্যমে UI তৈরি করতে পারি h :

let inputElem;
switch (config.type) {
  case 'range': {
    let { min, max, step } = config;
    inputElem = h(EditableInput, {
      type: 'number',
      min,
      max,
      step,
      …attrs
    });
    break;
  }
  case 'text':
    inputElem = h(EditableInput, attrs);
    break;
  case 'toggle': {
    inputElem = h('input', {
      type: 'checkbox',
      …attrs
    });
    break;
  }
  // …

একটি অসীম ইভেন্ট লুপে বারবার এই ফাংশনটি চালানোর মাধ্যমে, আমি সর্বদা সর্বশেষ তথ্য দেখানোর জন্য সেটিংস UI পেতে পারি, যখনই ব্যবহারকারীর দ্বারা ক্ষেত্রগুলির একটি সম্পাদনা করা হয় তখন ক্যামেরাতে কমান্ড পাঠাতে পারি।

পৃষ্ঠার ফোকাস বা এডিট স্টেটগুলিকে ব্যাহত না করেই প্রেক্ট শুধুমাত্র UI-এর পরিবর্তিত বিটের জন্য ফলাফলের পার্থক্য এবং DOM আপডেট করার যত্ন নিতে পারে। একটি সমস্যা যা অবশিষ্ট থাকে তা হল দ্বিমুখী ডেটা প্রবাহ। রিঅ্যাক্ট এবং প্রেক্টের মতো ফ্রেমওয়ার্কগুলি একমুখী ডেটা প্রবাহকে ঘিরে ডিজাইন করা হয়েছিল, কারণ এটি ডেটা সম্পর্কে যুক্তি করা এবং এটিকে পুনরায় চালানোর মধ্যে তুলনা করা অনেক সহজ করে তোলে, তবে আমি একটি বাহ্যিক উত্স - ক্যামেরা - সেটিংস আপডেট করার অনুমতি দিয়ে সেই প্রত্যাশা ভঙ্গ করছি যে কোনো সময় UI।

আমি বর্তমানে ব্যবহারকারীর দ্বারা সম্পাদিত যেকোন ইনপুট ক্ষেত্রগুলির জন্য UI আপডেটগুলি থেকে অপ্ট আউট করে এই সমস্যাটি নিয়ে কাজ করেছি:

/**
 * Wrapper around <input /> that doesn't update it while it's in focus to allow editing.
 */
class EditableInput extends Component {
  ref = createRef();

  shouldComponentUpdate() {
    return this.props.readonly || document.activeElement !== this.ref.current;
  }

  render(props) {
    return h('input', Object.assign(props, {ref: this.ref}));
  }
}

এইভাবে, যে কোনো প্রদত্ত ক্ষেত্রের একমাত্র মালিক সবসময় থাকে। হয় ব্যবহারকারী বর্তমানে এটি সম্পাদনা করছেন, এবং ক্যামেরা থেকে আপডেট হওয়া মানগুলি দ্বারা ব্যাহত হবে না, অথবা ক্যামেরাটি ফোকাসের বাইরে থাকা অবস্থায় ফিল্ডের মান আপডেট করছে।

একটি লাইভ "ভিডিও" ফিড তৈরি করা

মহামারী চলাকালীন, অনেক লোক অনলাইন মিটিংয়ে চলে গেছে। অন্যান্য জিনিসের মধ্যে, এটি ওয়েবক্যামের বাজারে ঘাটতির দিকে পরিচালিত করে। ল্যাপটপে অন্তর্নির্মিত ক্যামেরাগুলির তুলনায় একটি ভাল ভিডিও গুণমান পেতে এবং বলা ঘাটতির প্রতিক্রিয়া হিসাবে, অনেক DSLR এবং আয়নাবিহীন ক্যামেরার মালিকরা তাদের ফটোগ্রাফি ক্যামেরাগুলিকে ওয়েবক্যাম হিসাবে ব্যবহার করার উপায় খুঁজতে শুরু করেছেন। বেশ কিছু ক্যামেরা বিক্রেতা এমনকি এই উদ্দেশ্যের জন্য অফিসিয়াল ইউটিলিটি পাঠিয়েছে

অফিসিয়াল টুলের মতো, gPhoto2 ক্যামেরা থেকে স্থানীয়ভাবে সঞ্চিত ফাইলে বা সরাসরি ভার্চুয়াল ওয়েবক্যামে ভিডিও স্ট্রিমিং সমর্থন করে । আমি আমার ডেমোতে একটি লাইভ ভিউ প্রদান করতে সেই বৈশিষ্ট্যটি ব্যবহার করতে চেয়েছিলাম। যাইহোক, এটি কনসোল ইউটিলিটিতে উপলব্ধ থাকাকালীন, আমি libgphoto2 লাইব্রেরি API তে এটি কোথাও খুঁজে পাইনি।

কনসোল ইউটিলিটিতে সংশ্লিষ্ট ফাংশনের সোর্স কোডের দিকে তাকিয়ে, আমি দেখতে পেলাম যে এটি আসলে কোনও ভিডিও পাচ্ছে না, তবে পরিবর্তে একটি অবিরাম লুপে পৃথক JPEG চিত্র হিসাবে ক্যামেরার পূর্বরূপ পুনরুদ্ধার করতে থাকে এবং সেগুলি একে একে লিখতে থাকে। একটি M-JPEG স্ট্রীম গঠন করুন:

while (1) {
  const char *mime;
  r = gp_camera_capture_preview (p->camera, file, p->context);
  // …

আমি অবাক হয়ে গিয়েছিলাম যে এই পদ্ধতিটি মসৃণ রিয়েলটাইম ভিডিওর ছাপ পেতে যথেষ্ট দক্ষতার সাথে কাজ করে। সমস্ত অতিরিক্ত বিমূর্ততা এবং উপায়ে Asyncify সহ ওয়েব অ্যাপ্লিকেশনেও একই কার্যকারিতা মেলতে সক্ষম হওয়ার বিষয়ে আমি আরও বেশি সন্দিহান ছিলাম। যাইহোক, আমি যেভাবেই হোক চেষ্টা করার সিদ্ধান্ত নিয়েছি।

C++ দিকে আমি capturePreviewAsBlob() নামক একটি পদ্ধতি প্রকাশ করেছি যা একই gp_camera_capture_preview() ফাংশনকে আহ্বান করে এবং ফলস্বরূপ ইন-মেমরি ফাইলটিকে একটি Blob এ রূপান্তর করে যা অন্য ওয়েব API-তে আরও সহজে পাস করা যেতে পারে:

val capturePreviewAsBlob() {
  return gpp_rethrow([=]() {
    auto &file = get_file();

    gpp_try(gp_camera_capture_preview(camera.get(), &file, context.get()));

    auto params = blob_chunks_and_opts(file);
    return Blob.new_(std::move(params.first), std::move(params.second));
  });
}

জাভাস্ক্রিপ্টের দিকে, আমার কাছে একটি লুপ আছে, যা gPhoto2-এর মতো, যেটি Blob s হিসাবে প্রিভিউ ছবিগুলি পুনরুদ্ধার করে, createImageBitmap এর সাথে পটভূমিতে ডিকোড করে এবং পরবর্তী অ্যানিমেশন ফ্রেমে ক্যানভাসে স্থানান্তর করে :

while (this.canvasRef.current) {
  try {
    let blob = await this.props.getPreview();

    let img = await createImageBitmap(blob, { /* … */ });
    await new Promise(resolve => requestAnimationFrame(resolve));
    canvasCtx.transferFromImageBitmap(img);
  } catch (err) {
    // …
  }
}

এই আধুনিক APIগুলি ব্যবহার করে নিশ্চিত করে যে সমস্ত ডিকোডিং কাজ ব্যাকগ্রাউন্ডে সম্পন্ন হয়েছে, এবং ক্যানভাস তখনই আপডেট হয় যখন ছবি এবং ব্রাউজার উভয়ই অঙ্কনের জন্য সম্পূর্ণরূপে প্রস্তুত থাকে। এটি আমার ল্যাপটপে একটি সামঞ্জস্যপূর্ণ 30+ FPS অর্জন করেছে, যা gPhoto2 এবং অফিসিয়াল Sony সফ্টওয়্যার উভয়ের নেটিভ পারফরম্যান্সের সাথে মিলেছে।

ইউএসবি অ্যাক্সেস সিঙ্ক্রোনাইজ করা হচ্ছে

যখন একটি USB ডেটা স্থানান্তরের অনুরোধ করা হয় যখন অন্য একটি অপারেশন ইতিমধ্যেই চলছে, তখন এটি সাধারণত একটি "ডিভাইস ব্যস্ত" ত্রুটির কারণ হবে৷ যেহেতু পূর্বরূপ এবং সেটিংস UI নিয়মিতভাবে আপডেট হয়, এবং ব্যবহারকারী একই সময়ে একটি চিত্র ক্যাপচার বা সেটিংস পরিবর্তন করার চেষ্টা করতে পারে, তাই বিভিন্ন ক্রিয়াকলাপের মধ্যে এই ধরনের দ্বন্দ্ব খুব ঘন ঘন দেখা যায়।

এগুলি এড়াতে, আমাকে অ্যাপ্লিকেশনের মধ্যে সমস্ত অ্যাক্সেস সিঙ্ক্রোনাইজ করতে হবে। এর জন্য, আমি একটি প্রতিশ্রুতি-ভিত্তিক অ্যাসিঙ্ক সারি তৈরি করেছি:

let context = await new Module.Context();

let queue = Promise.resolve();

function schedule(op) {
  let res = queue.then(() => op(context));
  queue = res.catch(rethrowIfCritical);
  return res;
}

বিদ্যমান queue প্রতিশ্রুতির একটি then() কলব্যাকে প্রতিটি ক্রিয়াকলাপকে চেইন করে এবং queue নতুন মান হিসাবে শৃঙ্খলিত ফলাফল সংরক্ষণ করে, আমি নিশ্চিত করতে পারি যে সমস্ত ক্রিয়াকলাপ একের পর এক, ক্রমানুসারে এবং ওভারল্যাপ ছাড়াই সম্পাদিত হয়েছে।

যেকোনো অপারেশন ত্রুটি কলকারীকে ফেরত দেওয়া হয়, যখন গুরুতর (অপ্রত্যাশিত) ত্রুটিগুলি সম্পূর্ণ চেইনটিকে একটি প্রত্যাখ্যান প্রতিশ্রুতি হিসাবে চিহ্নিত করে এবং নিশ্চিত করে যে পরবর্তীতে কোনো নতুন অপারেশন নির্ধারিত হবে না।

মডিউল প্রসঙ্গটিকে একটি ব্যক্তিগত (অ-রপ্তানিযোগ্য) ভেরিয়েবলে রেখে, আমি schedule() কলের মাধ্যমে না গিয়ে অ্যাপের অন্য কোথাও দুর্ঘটনাক্রমে context অ্যাক্সেস করার ঝুঁকি কমিয়ে দিচ্ছি।

জিনিসগুলিকে একত্রে বাঁধতে, এখন ডিভাইসের প্রসঙ্গে প্রতিটি অ্যাক্সেসকে এইভাবে একটি schedule() কলের মধ্যে আবৃত করতে হবে:

let config = await this.connection.schedule((context) => context.configToJS());

এবং

this.connection.schedule((context) => context.captureImageAsFile());

এর পরে, সমস্ত অপারেশন দ্বন্দ্ব ছাড়াই সফলভাবে সম্পাদন করা হয়েছিল।

উপসংহার

আরো বাস্তবায়ন অন্তর্দৃষ্টি জন্য Github এ কোডবেস ব্রাউজ করতে নির্দ্বিধায়. আমি মার্কাস মেইসনারকে ধন্যবাদ জানাতে চাই gPhoto2 রক্ষণাবেক্ষণের জন্য এবং আমার আপস্ট্রিম PR-এর পর্যালোচনার জন্য।

এই পোস্টগুলিতে দেখায়, WebAssembly, Asyncify এবং Fugu APIগুলি এমনকি সবচেয়ে জটিল অ্যাপ্লিকেশনগুলির জন্য একটি সক্ষম সংকলন লক্ষ্য সরবরাহ করে। তারা আপনাকে একটি লাইব্রেরি বা একটি অ্যাপ্লিকেশান নিতে দেয় যা আগে একটি একক প্ল্যাটফর্মের জন্য তৈরি করা হয়েছিল এবং এটিকে ওয়েবে পোর্ট করতে দেয়, এটিকে ডেক্সটপ এবং মোবাইল ডিভাইস জুড়ে একইভাবে বিপুল সংখ্যক ব্যবহারকারীর কাছে উপলব্ধ করে।