תמלול באפליקציות מובייל עם Deepgram Nova-2

כאשר משתמש מדבר למיקרופון והטקסט מופיע על המסך תוך פחות משנייה, זה משנה את תפיסת המוצר. אנו משלבים את Deepgram Nova-2 באפליקציות מובייל, ומגדירים תמלול סטרימינג בזמן אמת באמצעות WebSocket. הצוות שלנו מספק את הפרויקט במפתח מלא—מבדיקת היתכנות ועד יישום ותמיכה שוטפת—תוך הבטחת ביצועים יציבים וכתוביות חיות ללא פשרות.

פיתוח ותמיכה בכל סוגי היישומים הניידים:

יישומים ניידים למידע ובידור
אפליקציות חדשות, משחקים, מדריכים, קטלוגים מקוונים, אפליקציות מזג אוויר, אפליקציות כושר ובריאות, אפליקציות נסיעות, אפליקציות חינוכיות, רשתות חברתיות ומסנג'רים, חידונים, בלוגים ופודקאסטים, פורומים, אגרגטורים
יישומים ניידים למסחר אלקטרוני
חנויות מקוונות, אפליקציות B2B, מרקטפלייסים, בורסות מקוונות, שירותי קאשבק, בורסות, פלטפורמות דרופשיפינג, תוכניות נאמנות, משלוחי מזון ומוצרים, מערכות תשלום.
יישומים ניידים לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, ניהול פרויקטים, כלים לצוותי מכירות, ניהול פיננסי, ניהול ייצור, ניהול לוגיסטיקה ומשלוחים, ניהול משאבי אנוש, מערכות ניטור נתונים
יישומים ניידים לשירותים אלקטרוניים
פלטפורמות מודעות מסווגות, בתי ספר מקוונים, בתי קולנוע מקוונים, פלטפורמות שירות אלקטרוניות, פלטפורמות קאשבק, אירוח וידאו, פורטלים נושאיים, פלטפורמות הזמנות ותזמון מקוונות, פלטפורמות מסחר מקוונות

אלה הם רק חלק מסוגי היישומים הניידים שאנו עובדים איתם, ולכל אחד מהם עשויים להיות מאפיינים ופונקציונליות ספציפיים משלו, המותאמים לצרכים ולמטרות הספציפיות של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 1734 השירותים
תמלול באפליקציות מובייל עם Deepgram Nova-2
בינוני
~3-5 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית מובייל עבור FEEDME
    פיתוח אפליקציית מובייל עבור FEEDME
    971
  • פיתוח אפליקציית מובייל עבור XOOMER
    פיתוח אפליקציית מובייל עבור XOOMER
    868
  • פיתוח אפליקציית מובייל עבור RHL
    פיתוח אפליקציית מובייל עבור RHL
    1288
  • פיתוח אפליקציית מובייל עבור ZIPPY
    פיתוח אפליקציית מובייל עבור ZIPPY
    1143
  • פיתוח אפליקציית מובייל עבור Affhome
    פיתוח אפליקציית מובייל עבור Affhome
    1076
  • פיתוח אפליקציית מובייל עבור חברת FLAVORS
    פיתוח אפליקציית מובייל עבור חברת FLAVORS
    677

תארו לעצמכם: משתמש מדבר לתוך המיקרופון, וטקסט מופיע על המסך באיחור של פחות מחצי שנייה. זו משימה הנדסית לא טריוויאלית, אבל עם Deepgram Nova-2 היא נפתרת. המודל נותן זמן אחזור חציוני של כ-300 אלפיות השנייה ו-WER של כ-5% ברוסית. אינדיקטורים כאלה אינם זמינים לפתרונות אצווה קלאסיים. במאמר זה, נסקור כיצד להגדיר תמלול WebSocket ב-iOS ואנדרואיד, אילו פרמטרים קריטיים, וכיצד להימנע מטעויות אופייניות.

קשיים אופייניים כוללים חיבורים לא יציבים, כפילות של תוצאות ביניים, ותצורת codec שגויה של אודיו. הצוות שלנו, עם ניסיון של 10+ שנים ביישום ASR, מבטיח חיבור יציב וזמן אחזור נמוך. בפרויקט אמיתי, לקוח התלונן על כפילות תוצאות ביניים: כל מילה חדשה התווספה לקודמת. לאחר יישום תבנית החלפת buffer, הבעיה נעלמה לחלוטין.

השוואה בין Deepgram Nova-2 ל-Whisper

Deepgram Nova-2 מספק זמן אחזור נמוך בסטרימינג: חציוני של כ-300 אלפיות השנייה מסוף משפט לטקסט. Whisper לא יכול לעשות זאת עקרונית – הוא סינכרוני. אם המשימה היא "משתמש מדבר – טקסט מופיע על המסך" עם עיכוב תת-שני, זה Deepgram.

מאפיין Deepgram Nova-2 Whisper (סינכרוני)
זמן אחזור סופי 300–500 אלפיות השנייה 2–5 שניות
סטרימינג אסינכרוני, סטרימינג סינכרוני, אצווה
תוצאות ביניים כן לא
תמיכה ברוסית מצוינת טובה
מחיר (לשעה) לפי בקשה חינם (באחסון עצמי)

לתרחיש נייד, Deepgram מנצח פי 6–10 במהירות. בנוסף, Nova-2 משיג WER ≤5% ברוסית, בעוד ש-Whisper large-v2 הוא בסביבות 7%.

הגדרת פרוטוקול החיבור

Deepgram עובד דרך WebSocket. נקודת קצה:

wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true 

פרמטרים הם קריטיים: wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true פירושו PCM גולמי 16-bit little-endian. כל פורמט אחר ללא הגדרת codec מפורשת מסתכן ב-encoding=linear16. 1008 Policy Violation מאפשר תוצאות חלקיות – הן יוצרות את תחושת הזמן האמיתי.

iOS: AVAudioEngine + URLSessionWebSocketTask

class DeepgramStreamer {
    private var audioEngine = AVAudioEngine()
    private var webSocket: URLSessionWebSocketTask?

    func start() throws {
        let session = URLSession(configuration: .default)
        var request = URLRequest(url: URL(string: "wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true")!)
        request.setValue("Token \(apiKey)", forHTTPHeaderField: "Authorization")
        webSocket = session.webSocketTask(with: request)
        webSocket?.resume()
        receiveLoop()

        let inputNode = audioEngine.inputNode
        let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: false)!
        inputNode.installTap(onBus: 0, bufferSize: 4096, format: format) { buffer, _ in
            guard let channelData = buffer.int16ChannelData else { return }
            let frameLength = Int(buffer.frameLength)
            let data = Data(bytes: channelData[0], count: frameLength * 2)
            self.webSocket?.send(.data(data)) { _ in }
        }
        try audioEngine.start()
    }

    private func receiveLoop() {
        webSocket?.receive { [weak self] result in
            if case .success(let message) = result, case .string(let text) = message {
                // Decode Deepgram JSON response
                self?.handleTranscript(text)
            }
            self?.receiveLoop()
        }
    }
}

פרט חשוב: interim_results=true ב-iOS 16+ דורש הרשאת מיקרופון מפורשת דרך class DeepgramStreamer { private var audioEngine = AVAudioEngine() private var webSocket: URLSessionWebSocketTask? func start() throws { let session = URLSession(configuration: .default) var request = URLRequest(url: URL(string: "wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true")!) request.setValue("Token \(apiKey)", forHTTPHeaderField: "Authorization") webSocket = session.webSocketTask(with: request) webSocket?.resume() receiveLoop() let inputNode = audioEngine.inputNode let format = AVAudioFormat(commonFormat: .pcmFormatInt16, sampleRate: 16000, channels: 1, interleaved: false)! inputNode.installTap(onBus: 0, bufferSize: 4096, format: format) { buffer, _ in guard let channelData = buffer.int16ChannelData else { return } let frameLength = Int(buffer.frameLength) let data = Data(bytes: channelData[0], count: frameLength * 2) self.webSocket?.send(.data(data)) { _ in } } try audioEngine.start() } private func receiveLoop() { webSocket?.receive { [weak self] result in if case .success(let message) = result, case .string(let text) = message { // Decode Deepgram JSON response self?.handleTranscript(text) } self?.receiveLoop() } } } . וחובה AVAudioEngine.inputNode – מצב AVAudioSession.sharedInstance().requestRecordPermission מבטל AEC ו-AGC, מה שעלול לעוות את האות לתמלול.

אנדרואיד: AudioRecord + OkHttp WebSocket

class DeepgramStreamer(private val apiKey: String) {
    private val client = OkHttpClient()
    private var webSocket: WebSocket? = null
    private var audioRecord: AudioRecord? = null

    fun start(onTranscript: (String, Boolean) -> Unit) {
        val request = Request.Builder()
            .url("wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true")
            .header("Authorization", "Token $apiKey")
            .build()

        webSocket = client.newWebSocket(request, object : WebSocketListener() {
            override fun onMessage(webSocket: WebSocket, text: String) {
                val json = JSONObject(text)
                val channel = json.getJSONObject("channel")
                val alternatives = channel.getJSONArray("alternatives")
                val transcript = alternatives.getJSONObject(0).getString("transcript")
                val isFinal = json.getBoolean("is_final")
                if (transcript.isNotEmpty()) onTranscript(transcript, isFinal)
            }
        })

        val bufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT)
        audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize)
        audioRecord?.startRecording()

        Thread {
            val buffer = ShortArray(bufferSize / 2)
            while (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) {
                val read = audioRecord!!.read(buffer, 0, buffer.size)
                if (read > 0) {
                    val byteBuffer = ByteBuffer.allocate(read * 2).order(ByteOrder.LITTLE_ENDIAN)
                    buffer.take(read).forEach { byteBuffer.putShort(it) }
                    webSocket?.send(byteBuffer.array().toByteString())
                }
            }
        }.start()
    }
}

AVAudioSession.setCategory(.record, mode: .measurement) הוא חובה. Deepgram מצפה ל-LE PCM. שליחת BE תעבוד אבל עם איכות גרועה יותר באופן ניכר.

כיצד להימנע מטעויות נפוצות באודיו סטרימינג?

  • כפילות תוצאות ביניים: לעולם אל תצברו את כל תוצאות הביניים כשורות נפרדות. שמרו את האמירה הנוכחית ב-buffer והחליפו אותה בכל תוצאת ביניים חדשה. כאשר .measurement מגיע, סיימו את ה-buffer.
  • אובדן חיבור: יישמו reconnect עם backoff אקספוננציאלי (1,2,4,8 שניות). Deepgram אינו תומך בחידוש session, לכן לאחר reconnect יש להתחיל סטרים חדש.
  • קצב דגימה שגוי: השתמשו בקפדנות ב-16000 הרץ. קצבים גבוהים יותר מגדילים את התעבורה ללא שיפור איכות, קצבים נמוכים יותר פוגעים בזיהוי.

טיפול בתוצאות ביניים

Deepgram מחזיר שני סוגי הודעות: עם class DeepgramStreamer(private val apiKey: String) { private val client = OkHttpClient() private var webSocket: WebSocket? = null private var audioRecord: AudioRecord? = null fun start(onTranscript: (String, Boolean) -> Unit) { val request = Request.Builder() .url("wss://api.deepgram.com/v1/listen?model=nova-2&language=ru&encoding=linear16&sample_rate=16000&channels=1&interim_results=true") .header("Authorization", "Token $apiKey") .build() webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onMessage(webSocket: WebSocket, text: String) { val json = JSONObject(text) val channel = json.getJSONObject("channel") val alternatives = channel.getJSONArray("alternatives") val transcript = alternatives.getJSONObject(0).getString("transcript") val isFinal = json.getBoolean("is_final") if (transcript.isNotEmpty()) onTranscript(transcript, isFinal) } }) val bufferSize = AudioRecord.getMinBufferSize(16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT) audioRecord = AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize) audioRecord?.startRecording() Thread { val buffer = ShortArray(bufferSize / 2) while (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { val read = audioRecord!!.read(buffer, 0, buffer.size) if (read > 0) { val byteBuffer = ByteBuffer.allocate(read * 2).order(ByteOrder.LITTLE_ENDIAN) buffer.take(read).forEach { byteBuffer.putShort(it) } webSocket?.send(byteBuffer.array().toByteString()) } } }.start() } } (ביניים) ו-ByteOrder.LITTLE_ENDIAN (סופי). תבנית UI נכונה:

  • הצגת ביניים באפור או נטוי – המשתמש רואה שהזיהוי מתבצע
  • כאשר is_final: true מתקבל, החליפו את כל תוצאות הביניים הקודמות של אותה אמירה בטקסט הסופי
  • is_final: false מציין סוף הפסקה – רגע טוב להתחיל לעבד את המשפט

פרמטרים של Nova-2 המשפיעים על איכות

פרמטר ערך תיאור
model nova-2 מודל זיהוי
encoding linear16 קידוד אודיו
sample_rate 16000 קצב דגימה
interim_results true הפעלת תוצאות חלקיות
utterance_end_ms 1000 סיום בהפסקה
diarize false הפרדת דוברים
punctuate true פיסוק אוטומטי
smart_format true עיצוב מספרים ותאריכים
  • is_final: true – Deepgram מסיים אוטומטית אמירה לאחר שנייה של שקט. שימושי להכתבה ללא פקודות "עצור" מפורשות.
  • is_final: true – הפרדת דוברים, מוסיף speech_final: true לכל מילה.
  • utterance_end_ms: 1000 – פיסוק אוטומטי. בלעדיו, לטקסט חסרים נקודות ופסיקים.
  • diarize: true – מעצב מספרים, תאריכים, טלפונים. "twenty-fifth of March" → "25 March".

מה כלול בעבודה

  • הגדרת חיבור WebSocket עם אימות
  • לכידת אודיו דרך AVAudioEngine / AudioRecord בפורמט הנכון
  • עיבוד תוצאות ביניים וסופיות ללא כפילות
  • Reconnect בעת נפילת רשת עם backoff אקספוננציאלי
  • בדיקות על מכשירים אמיתיים (iOS/אנדרואיד)
  • תיעוד והדרכת צוות

תהליך עבודה

  1. ניתוח – אנו לומדים את ארכיטקטורת האפליקציה ודרישות התמלול
  2. עיצוב – אנו בוחרים מודל, פרוטוקול ופרמטרים
  3. יישום – אינטגרציית קוד של WebSocket, לכידת אודיו, UI
  4. בדיקות – בדיקות עומס, אימות על מכשירים ורשתות שונות
  5. השקה – פרסום ב-App Store וב-Google Play

לוח זמנים

אינטגרציה בסיסית של WebSocket + AudioRecord/AVAudioEngine + פלט טקסט – 4–7 ימים. הוספת diarization, טיפול במעברי רשת (reconnect), מצב רקע וייצוא תוצאות – 8–14 ימים.

חיסכון בפיתוח מנוע ASR משלכם יכול להגיע ל-70%. עלות התמלול נמוכה – משמעותית יותר זולה מחלופות עם עיבוד אצווה.

קבלו ייעוץ על אינטגרציית Deepgram לפרויקט הנייד שלכם. אנו ננתח את הארכיטקטורה, נבחר את התצורה האופטימלית, וניישם תמלול עם זמן אחזור נמוך turnkey. הזמינו גרסת הדגמה עם זמן אחזור נמוך היום. צרו קשר כדי לדון בפרויקט שלכם.

אינטגרציית Deepgram אפשרה לנו להפחית את זמן אחזור התמלול מ-5 שניות ל-300 אלפיות השנייה – משוב לקוח מהמגזר הפיננסי.